package graph

import (
	"context"

	"github.com/alcionai/clues"

	"github.com/alcionai/corso/src/internal/data"
	"github.com/alcionai/corso/src/internal/m365/support"
	"github.com/alcionai/corso/src/pkg/count"
	"github.com/alcionai/corso/src/pkg/fault"
	"github.com/alcionai/corso/src/pkg/path"
)

var _ data.BackupCollection = prefixCollection{}

// TODO: move this out of graph.  /data would be a much better owner
// for a generic struct like this.  However, support.StatusUpdater makes
// it difficult to extract from this package in a generic way.
type prefixCollection struct {
	full  path.Path
	prev  path.Path
	su    support.StatusUpdater
	state data.CollectionState
}

func (c prefixCollection) Items(ctx context.Context, _ *fault.Bus) <-chan data.Item {
	res := make(chan data.Item)
	close(res)

	s := support.CreateStatus(ctx, support.Backup, 0, support.CollectionMetrics{}, "")
	c.su(s)

	return res
}

func (c prefixCollection) FullPath() path.Path {
	return c.full
}

func (c prefixCollection) PreviousPath() path.Path {
	return c.prev
}

func (c prefixCollection) State() data.CollectionState {
	return c.state
}

func (c prefixCollection) DoNotMergeItems() bool {
	return false
}

// ---------------------------------------------------------------------------
// base collections
// ---------------------------------------------------------------------------

func BaseCollections(
	ctx context.Context,
	colls []data.BackupCollection,
	tenant, rOwner string,
	service path.ServiceType,
	categories map[path.CategoryType]struct{},
	su support.StatusUpdater,
	counter *count.Bus,
	errs *fault.Bus,
) ([]data.BackupCollection, error) {
	var (
		res      = []data.BackupCollection{}
		el       = errs.Local()
		lastErr  error
		collKeys = map[string]struct{}{}
	)

	// won't catch deleted collections, since they have no FullPath
	for _, c := range colls {
		if c.FullPath() != nil {
			collKeys[c.FullPath().String()] = struct{}{}
		}
	}

	for cat := range categories {
		ictx := clues.Add(ctx, "base_service", service, "base_category", cat)

		full, err := path.BuildPrefix(tenant, rOwner, service, cat)
		if err != nil {
			// Shouldn't happen.
			err = clues.WrapWC(ictx, err, "making path").Label(count.BadPathPrefix)
			el.AddRecoverable(ictx, err)
			lastErr = err

			continue
		}

		// only add this collection if it doesn't already exist in the set.
		if _, ok := collKeys[full.String()]; !ok {
			res = append(res, &prefixCollection{
				prev:  full,
				full:  full,
				su:    su,
				state: data.StateOf(full, full, counter),
			})
		}
	}

	return res, lastErr
}

// ---------------------------------------------------------------------------
// prefix migration
// ---------------------------------------------------------------------------

// Creates a new collection that only handles prefix pathing.
func NewPrefixCollection(
	prev, full path.Path,
	su support.StatusUpdater,
	counter *count.Bus,
) (*prefixCollection, error) {
	if prev != nil {
		if len(prev.Item()) > 0 {
			return nil, clues.New("prefix collection previous path contains an item")
		}

		if len(prev.Folders()) > 0 {
			return nil, clues.New("prefix collection previous path contains folders")
		}
	}

	if full != nil {
		if len(full.Item()) > 0 {
			return nil, clues.New("prefix collection full path contains an item")
		}

		if len(full.Folders()) > 0 {
			return nil, clues.New("prefix collection full path contains folders")
		}
	}

	pc := &prefixCollection{
		prev:  prev,
		full:  full,
		su:    su,
		state: data.StateOf(prev, full, counter),
	}

	if pc.state == data.DeletedState {
		return nil, clues.New("collection attempted to delete prefix")
	}

	if pc.state == data.NewState {
		return nil, clues.New("collection attempted to create a new prefix")
	}

	return pc, nil
}
