package pages

import (
	"context"
	"fmt"
	"log"

	"github.com/gin-gonic/gin"

	"chat-switchboard/providers"
	"chat-switchboard/store"
)

// ── Types for template data ──────────────────

// ModelOption is a flat model for template dropdowns.
type ModelOption struct {
	ProviderConfigID string `json:"provider_config_id"`
	ProviderName     string `json:"provider_name"`
	ModelID          string `json:"model_id"`
	DisplayName      string `json:"display_name"`
	ModelType        string `json:"model_type"`
}

// ProviderOption for template dropdowns.
type ProviderOption struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

// TeamOption for template dropdowns.
type TeamOption struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
	MemberCount int    `json:"member_count,omitempty"`
	IsActive    bool   `json:"is_active"`
}

// ProviderDetail is an enriched provider for the providers table.
type ProviderDetail struct {
	ID         string `json:"id"`
	Name       string `json:"name"`
	Provider   string `json:"provider"`
	Scope      string `json:"scope"`
	Endpoint   string `json:"endpoint"`
	ModelCount int    `json:"model_count"`
}

// ProviderTypeOption for the provider type dropdown.
type ProviderTypeOption struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

// UserRow is a user for the admin users table.
type UserRow struct {
	ID          string `json:"id"`
	Username    string `json:"username"`
	Email       string `json:"email"`
	DisplayName string `json:"display_name"`
	Role        string `json:"role"`
	IsActive    bool   `json:"is_active"`
}

// RoleConfig holds a role's current settings.
type RoleConfig struct {
	Name     string         `json:"name"`
	Primary  *RoleSelection `json:"primary"`
	Fallback *RoleSelection `json:"fallback"`
}

// RoleSelection is provider + model pair for a role slot.
type RoleSelection struct {
	ProviderID string `json:"provider_config_id"`
	ModelID    string `json:"model_id"`
}

// ── Page data structs ────────────────────────
//
// v0.25.0: Each loader function is a "data provider" keyed by surface ID.
// The surface manifest's DataRequires field references these keys.
// Currently 1:1 (one loader per surface). Future: composite loaders
// that assemble data from multiple providers for dashboard-style surfaces.

// AdminPageData is what the admin surface templates receive.
type AdminPageData struct {
	Section         string               `json:"section"`
	Category        string               `json:"category"`
	Providers       []ProviderOption     `json:"providers"`
	ProviderDetails []ProviderDetail     `json:"provider_details,omitempty"`
	ProviderTypes   []ProviderTypeOption `json:"provider_types,omitempty"`
	Models          []ModelOption        `json:"models"`
	Teams           []TeamOption         `json:"teams"`
	Users           []UserRow            `json:"users,omitempty"`
	Roles           []RoleConfig         `json:"roles"`
	Policies        any                  `json:"policies,omitempty"`
}

// ChatPageData is what the chat surface templates receive.
type ChatPageData struct {
	ChatID string `json:"chat_id,omitempty"`
}

// NotesPageData provides context for the notes surface shell.
type NotesPageData struct {
	NoteID string `json:"NoteID,omitempty"`
}

// SettingsPageData is what the settings surface templates receive.
type SettingsPageData struct {
	Section string `json:"section"`

	// v0.22.7: Feature gates from admin config.
	// These control which nav links/tabs are visible in the settings surface.
	BYOKEnabled         bool `json:"BYOKEnabled"`
	UserPersonasEnabled bool `json:"UserPersonasEnabled"`
}

// ── Loader registration ──────────────────────

// TeamAdminPageData is what the team-admin surface receives.
type TeamAdminPageData struct {
	Section string `json:"section"`
}

// ProjectsPageData is what the projects surface receives.
type ProjectsPageData struct {
	ProjectID string `json:"project_id"`
}

func (e *Engine) registerLoaders() {
	e.RegisterLoader("admin", e.adminLoader)
	e.RegisterLoader("chat", e.chatLoader)
	e.RegisterLoader("notes", e.notesLoader)
	e.RegisterLoader("settings", e.settingsLoader)
	e.RegisterLoader("team-admin", e.teamAdminLoader)
	e.RegisterLoader("projects", e.projectsLoader)
}

// ListDataProviders returns the keys of all registered data providers.
// Used for manifest validation — DataRequires entries must match a key here.
func (e *Engine) ListDataProviders() []string {
	keys := make([]string, 0, len(e.loaders))
	for k := range e.loaders {
		keys = append(keys, k)
	}
	return keys
}

// ── Admin loader ─────────────────────────────
// Pre-loads ALL dropdown data. Fixes bugs #1 (model roles) and #2 (team scope).

func (e *Engine) adminLoader(c *gin.Context, s store.Stores) (any, error) {
	ctx := context.Background()
	section := c.Param("section")
	if section == "" {
		section = "users"
	}

	data := &AdminPageData{
		Section:  section,
		Category: sectionCategory(section),
	}

	// ── Providers (global scope) ─────────────
	if s.Providers != nil {
		configs, err := s.Providers.ListGlobal(ctx)
		if err != nil {
			log.Printf("[pages/admin] Failed to list providers: %v", err)
		} else {
			for _, pc := range configs {
				name := pc.Name
				if name == "" {
					name = pc.Provider
				}
				data.Providers = append(data.Providers, ProviderOption{
					ID:   pc.ID,
					Name: name,
				})
			}
		}
	}

	// ── Teams ────────────────────────────────
	if s.Teams != nil {
		teams, err := s.Teams.List(ctx)
		if err != nil {
			log.Printf("[pages/admin] Failed to list teams: %v", err)
		} else {
			for _, t := range teams {
				data.Teams = append(data.Teams, TeamOption{
					ID:          t.ID,
					Name:        t.Name,
					Description: t.Description,
					MemberCount: t.MemberCount,
					IsActive:    t.IsActive,
				})
			}
		}
	}

	// ── Full model catalog with model_type ───
	// This is what fixes bug #1: all models from all providers,
	// including embedding models, with their model_type intact.
	if s.Catalog != nil {
		entries, err := s.Catalog.ListAllGlobal(ctx)
		if err != nil {
			log.Printf("[pages/admin] Failed to list catalog: %v", err)
		} else {
			provNames := make(map[string]string, len(data.Providers))
			for _, p := range data.Providers {
				provNames[p.ID] = p.Name
			}
			for _, entry := range entries {
				mt := entry.ModelType
				if mt == "" {
					mt = "chat"
				}
				dn := entry.DisplayName
				if dn == "" {
					dn = entry.ModelID
				}
				data.Models = append(data.Models, ModelOption{
					ProviderConfigID: entry.ProviderConfigID,
					ProviderName:     provNames[entry.ProviderConfigID],
					ModelID:          entry.ModelID,
					DisplayName:      dn,
					ModelType:        mt,
				})
			}
		}
	}

	// ── Section-specific data ────────────────
	switch section {
	case "roles":
		data.Roles = e.loadRolesConfig(ctx, s)
	case "routing":
		if s.RoutingPolicies != nil {
			policies, err := s.RoutingPolicies.ListAll(ctx)
			if err == nil {
				data.Policies = policies
			}
		}
	case "providers":
		data.ProviderTypes = loadProviderTypes()
		data.ProviderDetails = e.loadProviderDetails(ctx, s, data.Providers, data.Models)
	case "users":
		data.Users = e.loadUsers(ctx, s)
	}

	return data, nil
}

// loadRolesConfig reads current role assignments.
func (e *Engine) loadRolesConfig(ctx context.Context, s store.Stores) []RoleConfig {
	roleNames := []string{"utility", "embedding"}
	roles := make([]RoleConfig, 0, len(roleNames))

	if s.GlobalConfig == nil {
		for _, name := range roleNames {
			roles = append(roles, RoleConfig{Name: name})
		}
		return roles
	}

	raw, err := s.GlobalConfig.Get(ctx, "model_roles")
	if err != nil || raw == nil {
		for _, name := range roleNames {
			roles = append(roles, RoleConfig{Name: name})
		}
		return roles
	}

	rolesMap, ok := raw["roles"].(map[string]any)
	if !ok {
		rolesMap = raw
	}

	for _, name := range roleNames {
		rc := RoleConfig{Name: name}
		if roleData, ok := rolesMap[name].(map[string]any); ok {
			if primary, ok := roleData["primary"].(map[string]any); ok {
				rc.Primary = &RoleSelection{
					ProviderID: fmt.Sprintf("%v", primary["provider_config_id"]),
					ModelID:    fmt.Sprintf("%v", primary["model_id"]),
				}
			}
			if fallback, ok := roleData["fallback"].(map[string]any); ok {
				rc.Fallback = &RoleSelection{
					ProviderID: fmt.Sprintf("%v", fallback["provider_config_id"]),
					ModelID:    fmt.Sprintf("%v", fallback["model_id"]),
				}
			}
		}
		roles = append(roles, rc)
	}

	return roles
}

// ── Chat loader ──────────────────────────────

func (e *Engine) chatLoader(c *gin.Context, s store.Stores) (any, error) {
	chatID := c.Param("chatID")
	return &ChatPageData{ChatID: chatID}, nil
}

// ── Admin helpers ────────────────────────────

// sectionCategory maps an admin section to its category tab.
func sectionCategory(section string) string {
	switch section {
	case "users", "teams", "groups":
		return "people"
	case "providers", "models", "personas", "roles", "knowledgeBases", "memory":
		return "ai"
	case "health", "routing", "capabilities":
		return "routing"
	case "settings", "storage", "packages", "channels", "broadcast":
		return "system"
	case "usage", "audit", "stats":
		return "monitoring"
	default:
		return "ai" // default landing
	}
}

// loadProviderTypes returns the registered provider type metadata.
func loadProviderTypes() []ProviderTypeOption {
	types := providers.ListTypes()
	out := make([]ProviderTypeOption, 0, len(types))
	for _, t := range types {
		out = append(out, ProviderTypeOption{ID: t.ID, Name: t.Name})
	}
	return out
}

// loadProviderDetails enriches providers with model counts.
func (e *Engine) loadProviderDetails(ctx context.Context, s store.Stores, provs []ProviderOption, models []ModelOption) []ProviderDetail {
	// Count models per provider
	counts := make(map[string]int)
	for _, m := range models {
		counts[m.ProviderConfigID]++
	}

	// Get full provider configs for endpoint/scope
	if s.Providers == nil {
		return nil
	}
	configs, err := s.Providers.ListGlobal(ctx)
	if err != nil {
		log.Printf("[pages/admin] Failed to list provider details: %v", err)
		return nil
	}

	out := make([]ProviderDetail, 0, len(configs))
	for _, pc := range configs {
		name := pc.Name
		if name == "" {
			name = pc.Provider
		}
		out = append(out, ProviderDetail{
			ID:         pc.ID,
			Name:       name,
			Provider:   pc.Provider,
			Scope:      pc.Scope,
			Endpoint:   pc.Endpoint,
			ModelCount: counts[pc.ID],
		})
	}
	return out
}

// loadUsers returns the user list for the admin users table.
func (e *Engine) loadUsers(ctx context.Context, s store.Stores) []UserRow {
	if s.Users == nil {
		return nil
	}
	users, _, err := s.Users.List(ctx, store.ListOptions{Limit: 500, Sort: "username", Order: "asc"})
	if err != nil {
		log.Printf("[pages/admin] Failed to list users: %v", err)
		return nil
	}
	out := make([]UserRow, 0, len(users))
	for _, u := range users {
		out = append(out, UserRow{
			ID:          u.ID,
			Username:    u.Username,
			Email:       u.Email,
			DisplayName: u.DisplayName,
			Role:        u.Role,
			IsActive:    u.IsActive,
		})
	}
	return out
}

// ── Notes loader ─────────────────────────────

func (e *Engine) notesLoader(c *gin.Context, s store.Stores) (any, error) {
	noteID := c.Param("noteId")
	return &NotesPageData{NoteID: noteID}, nil
}

// ── Settings loader ──────────────────────────
// v0.22.7: Reads feature gates from GlobalConfig to control
// which nav links/tabs are visible (BYOK, User Personas).

func (e *Engine) teamAdminLoader(c *gin.Context, s store.Stores) (any, error) {
	section := c.Param("section")
	if section == "" {
		section = "members"
	}
	return &TeamAdminPageData{Section: section}, nil
}

func (e *Engine) settingsLoader(c *gin.Context, s store.Stores) (any, error) {
	section := c.Param("section")
	if section == "" {
		section = "general"
	}

	data := &SettingsPageData{Section: section}

	// Read feature gates from policies (where admin settings saves them).
	// Keys: allow_user_byok, allow_user_personas
	if s.Policies != nil {
		ctx := context.Background()
		policies, err := s.Policies.GetAll(ctx)
		if err == nil {
			data.BYOKEnabled = policies["allow_user_byok"] == "true"
			data.UserPersonasEnabled = policies["allow_user_personas"] == "true"
		}
	}

	return data, nil
}

func (e *Engine) projectsLoader(c *gin.Context, s store.Stores) (any, error) {
	projectID := c.Param("id")
	return &ProjectsPageData{ProjectID: projectID}, nil
}
