package utils

import (
	"context"
	"net/url"
	"strings"

	"github.com/alcionai/clues"
	"github.com/spf13/cobra"

	"github.com/alcionai/corso/src/cli/flags"
	"github.com/alcionai/corso/src/pkg/logger"
	"github.com/alcionai/corso/src/pkg/selectors"
)

type SharePointOpts struct {
	SiteID []string
	WebURL []string

	Library            string
	FileName           []string // for libraries, to duplicate onedrive interface
	FolderPath         []string // for libraries, to duplicate onedrive interface
	FileCreatedAfter   string
	FileCreatedBefore  string
	FileModifiedAfter  string
	FileModifiedBefore string

	Lists              []string
	ListModifiedAfter  string
	ListModifiedBefore string
	ListCreatedBefore  string
	ListCreatedAfter   string
	AllowListsRestore  bool

	PageFolder []string
	Page       []string

	RestoreCfg RestoreCfgOpts
	ExportCfg  ExportCfgOpts

	Populated flags.PopulatedFlags
}

func (s SharePointOpts) GetFileTimeField(flag string) string {
	switch flag {
	case flags.FileCreatedAfterFN:
		return s.FileCreatedAfter
	case flags.FileCreatedBeforeFN:
		return s.FileCreatedBefore
	case flags.FileModifiedAfterFN:
		return s.FileModifiedAfter
	case flags.FileModifiedBeforeFN:
		return s.FileModifiedBefore
	case flags.ListModifiedAfterFN:
		return s.ListModifiedAfter
	case flags.ListModifiedBeforeFN:
		return s.ListModifiedBefore
	case flags.ListCreatedBeforeFN:
		return s.ListCreatedBefore
	case flags.ListCreatedAfterFN:
		return s.ListCreatedAfter
	default:
		return ""
	}
}

func MakeSharePointOpts(cmd *cobra.Command) SharePointOpts {
	return SharePointOpts{
		SiteID: flags.SiteIDFV,
		WebURL: flags.WebURLFV,

		Library:            flags.LibraryFV,
		FileName:           flags.FileNameFV,
		FolderPath:         flags.FolderPathFV,
		FileCreatedAfter:   flags.FileCreatedAfterFV,
		FileCreatedBefore:  flags.FileCreatedBeforeFV,
		FileModifiedAfter:  flags.FileModifiedAfterFV,
		FileModifiedBefore: flags.FileModifiedBeforeFV,

		Lists:              flags.ListFV,
		ListModifiedAfter:  flags.ListModifiedAfterFV,
		ListModifiedBefore: flags.ListModifiedBeforeFV,
		ListCreatedAfter:   flags.ListCreatedAfterFV,
		ListCreatedBefore:  flags.ListCreatedBeforeFV,
		AllowListsRestore:  flags.AllowListsRestoreFV,

		Page:       flags.PageFV,
		PageFolder: flags.PageFolderFV,

		RestoreCfg: makeRestoreCfgOpts(cmd),
		ExportCfg:  makeExportCfgOpts(cmd),

		// populated contains the list of flags that appear in the
		// command, according to pflags.  Use this to differentiate
		// between an "empty" and a "missing" value.
		Populated: flags.GetPopulatedFlags(cmd),
	}
}

func SharePointAllowedCategories() map[string]struct{} {
	return map[string]struct{}{
		flags.DataLibraries: {},
		flags.DataLists:     {},
	}
}

func AddCategories(sel *selectors.SharePointBackup, cats []string) *selectors.SharePointBackup {
	if len(cats) == 0 {
		sel.Include(sel.LibraryFolders(selectors.Any()), sel.Lists(selectors.Any()))
	}

	for _, d := range cats {
		switch d {
		case flags.DataLists:
			sel.Include(sel.Lists(selectors.Any()))
		case flags.DataLibraries:
			sel.Include(sel.LibraryFolders(selectors.Any()))
		}
	}

	return sel
}

// ValidateSharePointRestoreFlags checks common flags for correctness and interdependencies
func ValidateSharePointRestoreFlags(backupID string, opts SharePointOpts) error {
	if len(backupID) == 0 {
		return clues.New("a backup ID is required")
	}

	// ensure url can parse all weburls provided by --site.
	if _, ok := opts.Populated[flags.SiteFN]; ok {
		for _, wu := range opts.WebURL {
			if _, err := url.Parse(wu); err != nil {
				return clues.New("invalid site url: " + wu)
			}
		}
	}

	return validateCommonTimeFlags(opts)
}

// AddSharePointInfo adds the scope of the provided values to the selector's
// filter set
func AddSharePointInfo(
	sel *selectors.SharePointRestore,
	v string,
	f func(string) []selectors.SharePointScope,
) {
	if len(v) == 0 {
		return
	}

	sel.Filter(f(v))
}

// IncludeSharePointRestoreDataSelectors builds the common data-selector
// inclusions for SharePoint commands.
func IncludeSharePointRestoreDataSelectors(ctx context.Context, opts SharePointOpts) *selectors.SharePointRestore {
	sites := opts.SiteID

	folderPaths, fileNames := len(opts.FolderPath), len(opts.FileName)
	siteIDs, webUrls := len(opts.SiteID), len(opts.WebURL)
	lists := len(opts.Lists)
	pageFolders, pageItems := len(opts.PageFolder), len(opts.Page)

	if siteIDs == 0 {
		sites = selectors.Any()
	}

	sel := selectors.NewSharePointRestore(sites)

	if folderPaths+fileNames+webUrls+lists+pageFolders+pageItems == 0 {
		sel.Include(sel.AllData())
		return sel
	}

	if folderPaths+fileNames > 0 {
		if fileNames == 0 {
			opts.FileName = selectors.Any()
		}

		opts.FolderPath = trimFolderSlash(opts.FolderPath)
		containsFolders, prefixFolders := splitFoldersIntoContainsAndPrefix(opts.FolderPath)

		if len(containsFolders) > 0 {
			sel.Include(sel.LibraryItems(containsFolders, opts.FileName))
		}

		if len(prefixFolders) > 0 {
			sel.Include(sel.LibraryItems(prefixFolders, opts.FileName, selectors.PrefixMatch()))
		}
	}

	if lists > 0 {
		opts.Lists = trimFolderSlash(opts.Lists)
		sel.Include(sel.ListItems(opts.Lists, opts.Lists, selectors.StrictEqualMatch()))
		sel.Configure(selectors.Config{OnlyMatchItemNames: true})
	}

	if pageFolders+pageItems > 0 {
		if pageItems == 0 {
			opts.Page = selectors.Any()
		}

		opts.PageFolder = trimFolderSlash(opts.PageFolder)
		containsFolders, prefixFolders := splitFoldersIntoContainsAndPrefix(opts.PageFolder)

		if len(containsFolders) > 0 {
			sel.Include(sel.PageItems(containsFolders, opts.Page))
		}

		if len(prefixFolders) > 0 {
			sel.Include(sel.PageItems(prefixFolders, opts.Page, selectors.PrefixMatch()))
		}
	}

	if webUrls > 0 {
		urls := make([]string, 0, len(opts.WebURL))

		for _, wu := range opts.WebURL {
			// for normalization, ensure the site has a https:// prefix.
			wu = strings.TrimPrefix(wu, "https://")
			wu = strings.TrimPrefix(wu, "http://")

			// don't add a prefix to path-only values
			if len(wu) > 0 && wu != "*" && !strings.HasPrefix(wu, "/") {
				wu = "https://" + wu
			}

			u, err := url.Parse(wu)
			if err != nil {
				// shouldn't be possible to err, if we called validation first.
				logger.Ctx(ctx).With("web_url", wu).Error("malformed web url")
				continue
			}

			urls = append(urls, u.String())
		}

		sel.Include(sel.WebURL(urls))
	}

	return sel
}

// FilterSharePointRestoreInfoSelectors builds the common info-selector filters.
func FilterSharePointRestoreInfoSelectors(
	sel *selectors.SharePointRestore,
	opts SharePointOpts,
) {
	AddSharePointInfo(sel, opts.Library, sel.Library)
	AddSharePointInfo(sel, opts.FileCreatedAfter, sel.CreatedAfter)
	AddSharePointInfo(sel, opts.FileCreatedBefore, sel.CreatedBefore)
	AddSharePointInfo(sel, opts.FileModifiedAfter, sel.ModifiedAfter)
	AddSharePointInfo(sel, opts.FileModifiedBefore, sel.ModifiedBefore)
	AddSharePointInfo(sel, opts.ListModifiedAfter, sel.ListModifiedAfter)
	AddSharePointInfo(sel, opts.ListModifiedBefore, sel.ListModifiedBefore)
	AddSharePointInfo(sel, opts.ListCreatedAfter, sel.ListCreatedAfter)
	AddSharePointInfo(sel, opts.ListCreatedBefore, sel.ListCreatedBefore)
}
