package auth

import (
	"context"
	"time"

	"chat-switchboard/store"
)

// EveryoneGroupID is the stable ID of the implicit "Everyone" group seeded in
// migration 020. Every authenticated user receives its permissions without an
// explicit membership row.
const EveryoneGroupID = "00000000-0000-0000-0000-000000000001"

// Permission constants — domain.action convention.
const (
	PermModelUse        = "model.use"         // use models for completion
	PermModelSelectAny  = "model.select_any"  // use any enabled model (vs. group allowlist)
	PermKBRead          = "kb.read"           // search KBs through personas
	PermKBWrite         = "kb.write"          // upload/delete KB documents
	PermKBCreate        = "kb.create"         // create new knowledge bases
	PermChannelCreate   = "channel.create"    // create group/channel conversations
	PermChannelInvite   = "channel.invite"    // invite users to channels
	PermPersonaCreate   = "persona.create"    // create new personas
	PermPersonaManage   = "persona.manage"    // edit/delete team personas
	PermWorkflowCreate  = "workflow.create"   // create workflow definitions (v0.25.0)
	PermAdminView       = "admin.view"        // read-only admin panel access
	PermTokenUnlimited  = "token.unlimited"   // bypass token budgets
	PermTaskCreate      = "task.create"       // create scheduled tasks (v0.27.2)
	PermTaskAdmin       = "task.admin"        // manage all tasks, set global task config (v0.27.2)
	PermTaskAction      = "task.action"       // create non-LLM action tasks (v0.28.0)
	PermTaskStarlark    = "task.starlark"     // create Starlark tasks (pre-positioned for v0.29.0)
)

// AllPermissions is the complete set of valid permission strings.
// Used for validation in handlers and rendering checkboxes in admin UI.
var AllPermissions = []string{
	PermModelUse,
	PermModelSelectAny,
	PermKBRead,
	PermKBWrite,
	PermKBCreate,
	PermChannelCreate,
	PermChannelInvite,
	PermPersonaCreate,
	PermPersonaManage,
	PermWorkflowCreate,
	PermAdminView,
	PermTokenUnlimited,
	PermTaskCreate,
	PermTaskAdmin,
	PermTaskAction,
	PermTaskStarlark,
}

// ── Resolution ──────────────────────────────

// ResolvePermissions returns the effective permission set for a user.
// Always includes the Everyone group regardless of membership.
// Callers should short-circuit for role=admin before calling this.
func ResolvePermissions(ctx context.Context, stores store.Stores, userID string) (map[string]bool, error) {
	perms := make(map[string]bool)

	// Always apply Everyone group — no membership row required.
	if everyone, err := stores.Groups.GetByID(ctx, EveryoneGroupID); err == nil {
		for _, p := range everyone.Permissions {
			perms[p] = true
		}
	}

	// Union all explicit group memberships.
	groups, err := stores.Groups.ListForUser(ctx, userID)
	if err != nil {
		return perms, err
	}
	for _, g := range groups {
		for _, p := range g.Permissions {
			perms[p] = true
		}
	}

	return perms, nil
}

// ── Token Budget ────────────────────────────

// TokenBudget holds the resolved ceiling for a user and the time window it covers.
type TokenBudget struct {
	Limit  int64
	Period string // "daily" or "monthly"
}

// ResolveTokenBudget returns the most restrictive token budget across all of the
// user's groups. Returns nil if no group has a budget set (unrestricted).
// Callers should skip budget enforcement when providerScope == "personal" (BYOK).
func ResolveTokenBudget(ctx context.Context, stores store.Stores, userID string) (*TokenBudget, error) {
	groups, err := stores.Groups.ListForUser(ctx, userID)
	if err != nil {
		return nil, err
	}

	now := time.Now()
	isNewDay := now.Hour() < 1
	_ = isNewDay // used by callers for cache invalidation hints if needed

	var daily, monthly *int64
	for _, g := range groups {
		if g.TokenBudgetDaily != nil {
			if daily == nil || *g.TokenBudgetDaily < *daily {
				daily = g.TokenBudgetDaily
			}
		}
		if g.TokenBudgetMonthly != nil {
			if monthly == nil || *g.TokenBudgetMonthly < *monthly {
				monthly = g.TokenBudgetMonthly
			}
		}
	}

	// Return the most restrictive window. Daily wins when both are set
	// and daily is the binding constraint.
	if daily != nil {
		return &TokenBudget{Limit: *daily, Period: "daily"}, nil
	}
	if monthly != nil {
		return &TokenBudget{Limit: *monthly, Period: "monthly"}, nil
	}
	return nil, nil
}

// ── Model Allowlist ─────────────────────────

// ResolveModelAllowlist returns the union of allowed model IDs across all of the
// user's groups. Returns nil if the user is unrestricted (any group has a nil
// allowlist, which means "no restriction").
func ResolveModelAllowlist(ctx context.Context, stores store.Stores, userID string) (map[string]bool, error) {
	groups, err := stores.Groups.ListForUser(ctx, userID)
	if err != nil {
		return nil, err
	}

	// If any group has nil AllowedModels, the user is unrestricted.
	for _, g := range groups {
		if g.AllowedModels == nil {
			return nil, nil
		}
	}

	// All groups have explicit allowlists — union them.
	allowed := make(map[string]bool)
	for _, g := range groups {
		for _, m := range g.AllowedModels {
			allowed[m] = true
		}
	}
	// No groups at all → fall through to Everyone group check.
	if len(groups) == 0 {
		if everyone, err := stores.Groups.GetByID(ctx, EveryoneGroupID); err == nil {
			if everyone.AllowedModels == nil {
				return nil, nil // Everyone has no restriction
			}
			for _, m := range everyone.AllowedModels {
				allowed[m] = true
			}
		}
	}

	if len(allowed) == 0 {
		return nil, nil // empty allowlists across all groups = unrestricted
	}
	return allowed, nil
}
