package utils

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

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

type ExchangeOpts struct {
	Users []string

	Contact       []string
	ContactFolder []string
	ContactName   string

	Email               []string
	EmailFolder         []string
	EmailReceivedAfter  string
	EmailReceivedBefore string
	EmailSender         string
	EmailSubject        string

	Event             []string
	EventCalendar     []string
	EventOrganizer    string
	EventRecurs       string
	EventStartsAfter  string
	EventStartsBefore string
	EventSubject      string

	RestoreCfg RestoreCfgOpts

	Populated flags.PopulatedFlags
}

// populates an ExchangeOpts struct with the command's current flags.
func MakeExchangeOpts(cmd *cobra.Command) ExchangeOpts {
	return ExchangeOpts{
		Users: flags.UserFV,

		Contact:       flags.ContactFV,
		ContactFolder: flags.ContactFolderFV,
		ContactName:   flags.ContactNameFV,

		Email:               flags.EmailFV,
		EmailFolder:         flags.EmailFolderFV,
		EmailReceivedAfter:  flags.EmailReceivedAfterFV,
		EmailReceivedBefore: flags.EmailReceivedBeforeFV,
		EmailSender:         flags.EmailSenderFV,
		EmailSubject:        flags.EmailSubjectFV,

		Event:             flags.EventFV,
		EventCalendar:     flags.EventCalendarFV,
		EventOrganizer:    flags.EventOrganizerFV,
		EventRecurs:       flags.EventRecursFV,
		EventStartsAfter:  flags.EventStartsAfterFV,
		EventStartsBefore: flags.EventStartsBeforeFV,
		EventSubject:      flags.EventSubjectFV,

		RestoreCfg: makeRestoreCfgOpts(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),
	}
}

// AddExchangeInclude adds the scope of the provided values to the selector's
// inclusion set.  Any unpopulated slice will be replaced with selectors.Any()
// to act as a wildcard.
func AddExchangeInclude(
	sel *selectors.ExchangeRestore,
	folders, items []string,
	eisc selectors.ExchangeItemScopeConstructor,
) {
	lf, li := len(folders), len(items)

	// only use the inclusion if either a folder or item of
	// this type is specified.
	if lf+li == 0 {
		return
	}

	if li == 0 {
		items = selectors.Any()
	}

	containsFolders, prefixFolders := splitFoldersIntoContainsAndPrefix(folders)

	if len(containsFolders) > 0 {
		sel.Include(eisc(containsFolders, items))
	}

	if len(prefixFolders) > 0 {
		sel.Include(eisc(prefixFolders, items, selectors.PrefixMatch()))
	}
}

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

	sel.Filter(f(v))
}

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

	if _, ok := opts.Populated[flags.EmailReceivedAfterFN]; ok && !IsValidTimeFormat(opts.EmailReceivedAfter) {
		return clues.New("invalid time format for email-received-after")
	}

	if _, ok := opts.Populated[flags.EmailReceivedBeforeFN]; ok && !IsValidTimeFormat(opts.EmailReceivedBefore) {
		return clues.New("invalid time format for email-received-before")
	}

	if _, ok := opts.Populated[flags.EventStartsAfterFN]; ok && !IsValidTimeFormat(opts.EventStartsAfter) {
		return clues.New("invalid time format for event-starts-after")
	}

	if _, ok := opts.Populated[flags.EventStartsBeforeFN]; ok && !IsValidTimeFormat(opts.EventStartsBefore) {
		return clues.New("invalid time format for event-starts-before")
	}

	if _, ok := opts.Populated[flags.EventRecursFN]; ok && !IsValidBool(opts.EventRecurs) {
		return clues.New("invalid format for event-recurs")
	}

	return validateRestoreConfigFlags(flags.CollisionsFV, opts.RestoreCfg)
}

// IncludeExchangeRestoreDataSelectors builds the common data-selector
// inclusions for exchange commands.
func IncludeExchangeRestoreDataSelectors(opts ExchangeOpts) *selectors.ExchangeRestore {
	users := opts.Users
	if len(users) == 0 {
		users = selectors.Any()
	}

	sel := selectors.NewExchangeRestore(users)

	lc, lcf := len(opts.Contact), len(opts.ContactFolder)
	le, lef := len(opts.Email), len(opts.EmailFolder)
	lev, lec := len(opts.Event), len(opts.EventCalendar)
	// either scope the request to a set of users
	if lc+lcf+le+lef+lev+lec == 0 {
		sel.Include(sel.AllData())
		return sel
	}

	opts.EmailFolder = trimFolderSlash(opts.EmailFolder)

	// or add selectors for each type of data
	AddExchangeInclude(sel, opts.ContactFolder, opts.Contact, sel.Contacts)
	AddExchangeInclude(sel, opts.EmailFolder, opts.Email, sel.Mails)
	AddExchangeInclude(sel, opts.EventCalendar, opts.Event, sel.Events)

	return sel
}

// FilterExchangeRestoreInfoSelectors builds the common info-selector filters.
func FilterExchangeRestoreInfoSelectors(
	sel *selectors.ExchangeRestore,
	opts ExchangeOpts,
) {
	AddExchangeInfo(sel, opts.ContactName, sel.ContactName)
	AddExchangeInfo(sel, opts.EmailReceivedAfter, sel.MailReceivedAfter)
	AddExchangeInfo(sel, opts.EmailReceivedBefore, sel.MailReceivedBefore)
	AddExchangeInfo(sel, opts.EmailSender, sel.MailSender)
	AddExchangeInfo(sel, opts.EmailSubject, sel.MailSubject)
	AddExchangeInfo(sel, opts.EventOrganizer, sel.EventOrganizer)
	AddExchangeInfo(sel, opts.EventRecurs, sel.EventRecurs)
	AddExchangeInfo(sel, opts.EventStartsAfter, sel.EventStartsAfter)
	AddExchangeInfo(sel, opts.EventStartsBefore, sel.EventStartsBefore)
	AddExchangeInfo(sel, opts.EventSubject, sel.EventSubject)
}
