package onedrive

import (
	"context"
	"fmt"

	"github.com/alcionai/clues"

	"github.com/alcionai/corso/src/internal/common/prefixmatcher"
	"github.com/alcionai/corso/src/internal/data"
	"github.com/alcionai/corso/src/internal/m365/collection/drive"
	"github.com/alcionai/corso/src/internal/m365/support"
	"github.com/alcionai/corso/src/internal/observe"
	"github.com/alcionai/corso/src/internal/operations/inject"
	"github.com/alcionai/corso/src/internal/version"
	"github.com/alcionai/corso/src/pkg/account"
	"github.com/alcionai/corso/src/pkg/count"
	"github.com/alcionai/corso/src/pkg/fault"
	"github.com/alcionai/corso/src/pkg/logger"
	"github.com/alcionai/corso/src/pkg/path"
	"github.com/alcionai/corso/src/pkg/services/m365/api"
	"github.com/alcionai/corso/src/pkg/services/m365/api/graph"
)

type oneDriveBackup struct{}

// NewBackup provides a struct that matches standard apis
// across m365/service handlers.
func NewBackup() *oneDriveBackup {
	return &oneDriveBackup{}
}

func (oneDriveBackup) ProduceBackupCollections(
	ctx context.Context,
	bpc inject.BackupProducerConfig,
	ac api.Client,
	creds account.M365Config,
	su support.StatusUpdater,
	counter *count.Bus,
	errs *fault.Bus,
) ([]data.BackupCollection, *prefixmatcher.StringSetMatcher, bool, error) {
	odb, err := bpc.Selector.ToOneDriveBackup()
	if err != nil {
		return nil, nil, false, clues.WrapWC(ctx, err, "parsing selector")
	}

	var (
		el                   = errs.Local()
		tenantID             = creds.AzureTenantID
		categories           = map[path.CategoryType]struct{}{}
		collections          = []data.BackupCollection{}
		ssmb                 = prefixmatcher.NewStringSetBuilder()
		odcs                 []data.BackupCollection
		canUsePreviousBackup bool
	)

	// for each scope that includes oneDrive items, get all
	for _, scope := range odb.Scopes() {
		if el.Failure() != nil {
			break
		}

		logger.Ctx(ctx).Debug("creating OneDrive collections")

		nc := drive.NewCollections(
			drive.NewUserDriveBackupHandler(ac.Drives(), bpc.ProtectedResource.ID(), scope),
			tenantID,
			bpc.ProtectedResource,
			su,
			bpc.Options,
			counter)

		progressMessage := observe.MessageWithCompletion(
			ctx,
			observe.ProgressCfg{
				Indent:            1,
				CompletionMessage: func() string { return fmt.Sprintf("(found %d files)", nc.NumFiles) },
			},
			path.FilesCategory.HumanString())
		defer close(progressMessage)

		odcs, canUsePreviousBackup, err = nc.Get(ctx, bpc.MetadataCollections, ssmb, errs)
		if err != nil {
			el.AddRecoverable(ctx, clues.Stack(err).Label(fault.LabelForceNoBackupCreation))
		}

		categories[scope.Category().PathType()] = struct{}{}

		collections = append(collections, odcs...)
	}

	mcs, err := migrationCollections(bpc, tenantID, su, counter)
	if err != nil {
		return nil, nil, false, err
	}

	collections = append(collections, mcs...)

	if len(collections) > 0 {
		baseCols, err := graph.BaseCollections(
			ctx,
			collections,
			tenantID,
			bpc.ProtectedResource.ID(),
			path.OneDriveService,
			categories,
			su,
			counter,
			errs)
		if err != nil {
			return nil, nil, false, err
		}

		collections = append(collections, baseCols...)
	}

	logger.Ctx(ctx).Infow("produced collections", "stats", counter.Values())

	return collections, ssmb.ToReader(), canUsePreviousBackup, el.Failure()
}

// adds data migrations to the collection set.
func migrationCollections(
	bpc inject.BackupProducerConfig,
	tenant string,
	su support.StatusUpdater,
	counter *count.Bus,
) ([]data.BackupCollection, error) {
	// assume a version < 0 implies no prior backup, thus nothing to migrate.
	if version.IsNoBackup(bpc.LastBackupVersion) {
		return nil, nil
	}

	if bpc.LastBackupVersion >= version.All8MigrateUserPNToID {
		return nil, nil
	}

	counter.Inc(count.RequiresUserPnToIDMigration)

	// unlike exchange, which enumerates all folders on every
	// backup, onedrive needs to force the owner PN -> ID migration
	mc, err := path.BuildPrefix(
		tenant,
		bpc.ProtectedResource.ID(),
		path.OneDriveService,
		path.FilesCategory)
	if err != nil {
		return nil, clues.Wrap(err, "creating user id migration path")
	}

	mpc, err := path.BuildPrefix(
		tenant,
		bpc.ProtectedResource.Name(),
		path.OneDriveService,
		path.FilesCategory)
	if err != nil {
		return nil, clues.Wrap(err, "creating user name migration path")
	}

	mgn, err := graph.NewPrefixCollection(mpc, mc, su, counter)
	if err != nil {
		return nil, clues.Wrap(err, "creating migration collection")
	}

	return []data.BackupCollection{mgn}, nil
}
