package capabilities

import (
	"context"
	"log"

	"chat-switchboard/models"
	"chat-switchboard/store"
)

// ModelsForUser returns all models and Personas visible to a user.
//
// Visibility is controlled by the three-state visibility field on each
// catalog entry (enabled / team / disabled), applied per provider scope:
//
//	Tier      | enabled        | team                 | disabled
//	----------+----------------+----------------------+---------
//	Global    | All users      | Team admin → personas | Hidden
//	Team      | Team members   | Team admin → personas | Hidden
//	Personal  | Owner direct   | N/A                  | Hidden
//
// Sources aggregated:
//  1. Global catalog: enabled models → all authenticated users
//  2. Team catalog:   enabled models → team members
//  3. Personal BYOK:  enabled models → owner (if allow_user_byok)
//  4. Personas:       global + team-scoped + personal + shared
//  5. User hidden preferences applied last
func ModelsForUser(ctx context.Context, stores store.Stores, userID string) ([]models.UserModel, error) {
	result := make([]models.UserModel, 0) // never nil — serializes as [] not null

	// Load policies once
	policies, err := stores.Policies.GetAll(ctx)
	if err != nil {
		return nil, err
	}
	allowBYOK := policies["allow_user_byok"] == "true"

	// Load user's hidden preferences
	hiddenMap, err := stores.UserSettings.GetHiddenModelIDs(ctx, userID)
	if err != nil {
		log.Printf("warn: failed to load user model settings: %v", err)
		hiddenMap = make(map[string]bool)
	}

	// Get user's team IDs
	teamIDs, err := stores.Teams.GetUserTeamIDs(ctx, userID)
	if err != nil {
		log.Printf("warn: failed to load user teams: %v", err)
	}

	// ── 1. Global enabled catalog models → all users ────
	globalModels, err := stores.Catalog.ListVisible(ctx)
	if err != nil {
		return nil, err
	}

	// Build provider name lookup for global providers
	globalProviders, _ := stores.Providers.ListGlobal(ctx)
	providerMap := make(map[string]models.ProviderConfig)
	for _, p := range globalProviders {
		providerMap[p.ID] = p
	}

	countGlobal := len(globalModels)
	for _, entry := range globalModels {
		caps := ResolveIntrinsic(entry.ModelID, &entry.Capabilities, nil)
		prov := providerMap[entry.ProviderConfigID]

		result = append(result, models.UserModel{
			ID:               entry.ModelID,
			DisplayName:      displayName(entry.DisplayName, entry.ModelID),
			ModelID:          entry.ModelID,
			ModelType:        entry.ModelType,
			Source:           "catalog",
			ProviderConfigID: entry.ProviderConfigID,
			ConfigID:         entry.ProviderConfigID,
			ProviderName:     prov.Name,
			ProviderType:     prov.Provider,
			Capabilities:     caps,
			Pricing:          entry.Pricing,
			Scope:            models.ScopeGlobal,
			Hidden:           hiddenMap[models.CompositeModelKey(entry.ProviderConfigID, entry.ModelID)],
		})
	}

	// ── 2. Team enabled catalog models → team members ────
	countTeam := 0
	for _, teamID := range teamIDs {
		teamProviders, err := stores.Providers.ListForTeam(ctx, teamID)
		if err != nil {
			log.Printf("warn: failed to load team %s providers: %v", teamID, err)
			continue
		}
		for _, prov := range teamProviders {
			if !prov.IsActive {
				continue
			}
			entries, err := stores.Catalog.ListEnabledForProvider(ctx, prov.ID)
			if err != nil {
				continue
			}
			for _, entry := range entries {
				caps := ResolveIntrinsic(entry.ModelID, &entry.Capabilities, nil)
				result = append(result, models.UserModel{
					ID:               entry.ModelID,
					DisplayName:      displayName(entry.DisplayName, entry.ModelID),
					ModelID:          entry.ModelID,
					ModelType:        entry.ModelType,
					Source:           "catalog",
					ProviderConfigID: prov.ID,
					ConfigID:         prov.ID,
					ProviderName:     prov.Name,
					ProviderType:     prov.Provider,
					Capabilities:     caps,
					Pricing:          entry.Pricing,
					Scope:            models.ScopeTeam,
					OwnerID:          prov.OwnerID,
					Hidden:           hiddenMap[models.CompositeModelKey(entry.ProviderConfigID, entry.ModelID)],
				})
				countTeam++
			}
		}
	}

	// ── 3. Personal BYOK enabled models → owner ────
	countPersonal := 0
	if allowBYOK {
		personalProviders, err := stores.Providers.ListForUser(ctx, userID)
		if err != nil {
			log.Printf("warn: failed to load personal providers: %v", err)
		} else {
			for _, prov := range personalProviders {
				if !prov.IsActive {
					continue
				}
				entries, err := stores.Catalog.ListEnabledForProvider(ctx, prov.ID)
				if err != nil {
					continue
				}
				for _, entry := range entries {
					caps := ResolveIntrinsic(entry.ModelID, &entry.Capabilities, nil)
					result = append(result, models.UserModel{
						ID:               entry.ModelID,
						DisplayName:      displayName(entry.DisplayName, entry.ModelID),
						ModelID:          entry.ModelID,
						ModelType:        entry.ModelType,
						Source:           "catalog",
						ProviderConfigID: prov.ID,
						ConfigID:         prov.ID,
						ProviderName:     prov.Name,
						ProviderType:     prov.Provider,
						Capabilities:     caps,
						Pricing:          entry.Pricing,
						Scope:            models.ScopePersonal,
						OwnerID:          &userID,
						Hidden:           hiddenMap[models.CompositeModelKey(entry.ProviderConfigID, entry.ModelID)],
					})
					countPersonal++
				}
			}
		}
	}

	// ── 4. Personas (always resolved) ────────────────
	personas, err := stores.Personas.ListForUser(ctx, userID)
	if err != nil {
		log.Printf("warn: failed to load personas: %v", err)
	} else {
		for _, p := range personas {
			// Resolve base model capabilities
			var catalogCaps *models.ModelCapabilities
			if p.ProviderConfigID != nil {
				if entry, err := stores.Catalog.GetByModelID(ctx, *p.ProviderConfigID, p.BaseModelID); err == nil {
					catalogCaps = &entry.Capabilities
				}
			}
			// Fallback: look up any provider's catalog entry for this model
			// (covers auto-resolve personas where provider_config_id is NULL)
			if catalogCaps == nil {
				if entry, err := stores.Catalog.GetByModelIDAny(ctx, p.BaseModelID); err == nil {
					catalogCaps = &entry.Capabilities
				}
			}
			caps := ResolveIntrinsic(p.BaseModelID, catalogCaps, nil)

			// Load tool grants
			toolGrants, _ := stores.Personas.GetToolGrants(ctx, p.ID)

			if len(toolGrants) > 0 {
				caps.ToolCalling = true
			}

			// Look up provider info
			var provName, provType string
			if p.ProviderConfigID != nil {
				if prov, err := stores.Providers.GetByID(ctx, *p.ProviderConfigID); err == nil {
					provName = prov.Name
					provType = prov.Provider
				}
			}

			result = append(result, models.UserModel{
				ID:               p.ID,
				DisplayName:      p.Name,
				ModelID:          p.BaseModelID,
				Source:           "persona",
				ProviderConfigID: deref(p.ProviderConfigID),
				ConfigID:         deref(p.ProviderConfigID),
				ProviderName:     provName,
				ProviderType:     provType,
				Capabilities:     caps,
				IsPersona:        true,
				PersonaID:        p.ID,
				PersonaHandle:    p.Handle,
				PersonaScope:     p.Scope,
				PersonaAvatar:    p.Avatar,
				PersonaTeamName:  teamName(p, stores, ctx),
				Description:      p.Description,
				Icon:             p.Icon,
				Avatar:           p.Avatar,
				SystemPrompt:     p.SystemPrompt,
				Temperature:      p.Temperature,
				MaxTokens:        p.MaxTokens,
				ToolGrants:       toolGrants,
				Scope:            p.Scope,
				OwnerID:          p.OwnerID,
				Hidden:           false, // ICD §11.2: persona visibility via grants, not model prefs
			})
		}
	}

	countPersonas := len(result) - countGlobal - countTeam - countPersonal
	log.Printf("📋 ModelsForUser(%s): %d global, %d team, %d personal, %d personas → %d total",
		userID, countGlobal, countTeam, countPersonal, countPersonas, len(result))

	return result, nil
}

// ResolveForPersona returns effective capabilities for a specific Persona.
// Used at completion time to determine what tools/features are available.
func ResolveForPersona(ctx context.Context, stores store.Stores, persona *models.Persona) (models.ModelCapabilities, []string, error) {
	var catalogCaps *models.ModelCapabilities
	if persona.ProviderConfigID != nil {
		if entry, err := stores.Catalog.GetByModelID(ctx, *persona.ProviderConfigID, persona.BaseModelID); err == nil {
			catalogCaps = &entry.Capabilities
		}
	}
	// Fallback: any provider's catalog entry (auto-resolve personas)
	if catalogCaps == nil {
		if entry, err := stores.Catalog.GetByModelIDAny(ctx, persona.BaseModelID); err == nil {
			catalogCaps = &entry.Capabilities
		}
	}
	caps := ResolveIntrinsic(persona.BaseModelID, catalogCaps, nil)

	toolGrants, err := stores.Personas.GetToolGrants(ctx, persona.ID)
	if err != nil {
		return caps, nil, err
	}

	return caps, toolGrants, nil
}

func displayName(name, modelID string) string {
	if name != "" {
		return name
	}
	return modelID
}

func deref(s *string) string {
	if s == nil {
		return ""
	}
	return *s
}

// teamName resolves the team_name for a persona's owner (if team-scoped).
func teamName(p models.Persona, stores store.Stores, ctx context.Context) string {
	if p.Scope != models.ScopeTeam || p.OwnerID == nil {
		return ""
	}
	team, err := stores.Teams.GetByID(ctx, *p.OwnerID)
	if err != nil {
		return ""
	}
	return team.Name
}
