package biz

import (
	"context"
	"time"
)

type API struct {
	ID          uint
	CreatedAt   time.Time
	UpdatedAt   time.Time
	Path        string
	Description string
	APIGroup    string
	Method      string
	OrderKey    string
	Desc        bool
	StrictAll   bool
}

type APIRepo interface {
	CreateAPI(context.Context, *API) error
	UpdateAPI(context.Context, *API) error
	DeleteAPIs(context.Context, []uint) error
	FindAPI(context.Context, uint) (*API, error)
	ListAPIs(context.Context, int, int, *API) ([]*API, int64, error)
	APIRoleIDs(context.Context, string, string) ([]uint, error)
	SetAPIRoles(context.Context, string, string, []uint) error
	CheckPolicyStore(context.Context) error
	Authorize(context.Context, uint, string, string) (bool, error)
	PolicyPaths(context.Context, uint) ([]*API, error)
	SetPolicyPaths(context.Context, uint, []*API) error
	IgnoredAPIs(context.Context) ([]*API, error)
	SetAPIIgnored(context.Context, string, string, bool) error
	ApplyAPISync(context.Context, []*API, []*API) error
}

type APIUsecase struct{ APIRepo }

func NewAPIUsecase(repo APIRepo) *APIUsecase { return &APIUsecase{APIRepo: repo} }

func (uc *APIUsecase) FreshCasbin(ctx context.Context) error {
	return uc.CheckPolicyStore(ctx)
}

// DeleteAPI preserves the single-delete contract used by the legacy admin:
// the target is looked up first, so deleting a missing API returns the
// repository's not-found error instead of silently succeeding on an empty
// UPDATE/DELETE. Bulk deletion intentionally keeps separate semantics.
func (uc *APIUsecase) DeleteAPI(ctx context.Context, id uint) error {
	if _, err := uc.FindAPI(ctx, id); err != nil {
		return err
	}
	return uc.DeleteAPIs(ctx, []uint{id})
}

type APISyncDiff struct {
	Added   []*API
	Deleted []*API
	Ignored []*API
}

func (uc *APIUsecase) SyncAPIs(ctx context.Context, routes []*API) (*APISyncDiff, error) {
	stored, _, err := uc.ListAPIs(ctx, 0, 0, nil)
	if err != nil {
		return nil, err
	}
	ignored, err := uc.IgnoredAPIs(ctx)
	if err != nil {
		return nil, err
	}
	key := func(value *API) string { return value.Method + " " + value.Path }
	ignoreSet := make(map[string]bool, len(ignored))
	for _, item := range ignored {
		ignoreSet[key(item)] = true
	}
	routeSet := make(map[string]*API, len(routes))
	for _, item := range routes {
		if !ignoreSet[key(item)] {
			routeSet[key(item)] = item
		}
	}
	storedSet := make(map[string]*API, len(stored))
	for _, item := range stored {
		storedSet[key(item)] = item
	}
	// The sync API always returns JSON arrays for all three collections,
	// including when no routes were added or removed.
	diff := &APISyncDiff{
		Added:   make([]*API, 0),
		Deleted: make([]*API, 0),
		Ignored: ignored,
	}
	added := make(map[string]bool)
	for _, item := range routes {
		routeKey := key(item)
		if ignoreSet[routeKey] || added[routeKey] {
			continue
		}
		if storedSet[routeKey] == nil {
			diff.Added = append(diff.Added, item)
			added[routeKey] = true
		}
	}
	for _, item := range stored {
		storedKey := key(item)
		// Ignoring an API removes it from the in-memory route comparison. If it
		// already exists in sys_apis, the reference implementation therefore
		// returns it in deleteApis while also returning it in ignoreApis.
		if routeSet[storedKey] == nil {
			diff.Deleted = append(diff.Deleted, item)
		}
	}
	return diff, nil
}
