package handlers

import (
	"context"
	"net/http"

	"github.com/gin-gonic/gin"

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

// ── Routing Admin Handler ───────────────────

type RoutingAdminHandler struct {
	stores      store.Stores
	evaluator   *routing.Evaluator
	healthStore health.Store
}

func NewRoutingAdminHandler(stores store.Stores, evaluator *routing.Evaluator, hs health.Store) *RoutingAdminHandler {
	return &RoutingAdminHandler{stores: stores, evaluator: evaluator, healthStore: hs}
}

// ── CRUD ────────────────────────────────────

// ListPolicies returns all routing policies.
// GET /api/v1/admin/routing/policies
func (h *RoutingAdminHandler) ListPolicies(c *gin.Context) {
	if h.stores.RoutingPolicies == nil {
		c.JSON(http.StatusOK, gin.H{"data": []models.RoutingPolicy{}})
		return
	}

	policies, err := h.stores.RoutingPolicies.ListAll(context.Background())
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list policies: " + err.Error()})
		return
	}
	if policies == nil {
		policies = []models.RoutingPolicy{}
	}
	c.JSON(http.StatusOK, gin.H{"data": policies})
}

// GetPolicy returns a single routing policy.
// GET /api/v1/admin/routing/policies/:id
func (h *RoutingAdminHandler) GetPolicy(c *gin.Context) {
	if h.stores.RoutingPolicies == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "routing not available"})
		return
	}

	p, err := h.stores.RoutingPolicies.GetByID(context.Background(), c.Param("id"))
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "policy not found"})
		return
	}
	c.JSON(http.StatusOK, p)
}

// CreatePolicy creates a new routing policy.
// POST /api/v1/admin/routing/policies
func (h *RoutingAdminHandler) CreatePolicy(c *gin.Context) {
	if h.stores.RoutingPolicies == nil {
		c.JSON(http.StatusServiceUnavailable, gin.H{"error": "routing not available"})
		return
	}

	var p models.RoutingPolicy
	if err := c.ShouldBindJSON(&p); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Validate policy type
	switch routing.PolicyType(p.Type) {
	case routing.PolicyProviderPrefer, routing.PolicyTeamRoute,
		routing.PolicyCostLimit, routing.PolicyModelAlias,
		routing.PolicyCapabilityMatch:
		// valid
	default:
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid policy_type: " + p.Type})
		return
	}

	// Validate scope
	if p.Scope != "global" && p.Scope != "team" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "scope must be 'global' or 'team'"})
		return
	}
	if p.Scope == "team" && (p.TeamID == nil || *p.TeamID == "") {
		c.JSON(http.StatusBadRequest, gin.H{"error": "team_id required for team-scoped policies"})
		return
	}

	if err := h.stores.RoutingPolicies.Create(context.Background(), &p); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create policy: " + err.Error()})
		return
	}

	c.JSON(http.StatusCreated, p)
}

// UpdatePolicy updates an existing routing policy.
// PUT /api/v1/admin/routing/policies/:id
func (h *RoutingAdminHandler) UpdatePolicy(c *gin.Context) {
	if h.stores.RoutingPolicies == nil {
		c.JSON(http.StatusServiceUnavailable, gin.H{"error": "routing not available"})
		return
	}

	id := c.Param("id")

	// Verify exists
	if _, err := h.stores.RoutingPolicies.GetByID(context.Background(), id); err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "policy not found"})
		return
	}

	var p models.RoutingPolicy
	if err := c.ShouldBindJSON(&p); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	p.ID = id

	if err := h.stores.RoutingPolicies.Update(context.Background(), &p); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update policy: " + err.Error()})
		return
	}

	c.JSON(http.StatusOK, p)
}

// DeletePolicy removes a routing policy.
// DELETE /api/v1/admin/routing/policies/:id
func (h *RoutingAdminHandler) DeletePolicy(c *gin.Context) {
	if h.stores.RoutingPolicies == nil {
		c.JSON(http.StatusServiceUnavailable, gin.H{"error": "routing not available"})
		return
	}

	if err := h.stores.RoutingPolicies.Delete(context.Background(), c.Param("id")); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete policy: " + err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{"deleted": true})
}

// ── Dry-Run Test ────────────────────────────

type routingTestRequest struct {
	Model   string   `json:"model" binding:"required"`
	UserID  string   `json:"user_id" binding:"required"`
	TeamIDs []string `json:"team_ids,omitempty"`
}

// TestRouting performs a dry-run policy evaluation for a given model + user.
// POST /api/v1/admin/routing/test
func (h *RoutingAdminHandler) TestRouting(c *gin.Context) {
	if h.stores.RoutingPolicies == nil {
		c.JSON(http.StatusServiceUnavailable, gin.H{"error": "routing not available"})
		return
	}

	var req routingTestRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Load active policies
	dbPolicies, err := h.stores.RoutingPolicies.ListActive(context.Background())
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load policies"})
		return
	}
	policies := routing.FromModels(dbPolicies)

	// Build candidates from all active provider configs
	candidates, err := h.buildCandidates(c)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to build candidates: " + err.Error()})
		return
	}

	// Build health status
	healthStatus := make(map[string]models.ProviderStatus)
	if h.healthStore != nil {
		windows, _ := h.healthStore.ListAllCurrent(context.Background())
		for _, w := range windows {
			healthStatus[w.ProviderConfigID] = health.DeriveStatus(w.ErrorRate(), w.RequestCount)
		}
	}

	ctx := &routing.Context{
		UserID:       req.UserID,
		TeamIDs:      req.TeamIDs,
		Model:        req.Model,
		HealthStatus: healthStatus,
		Pricing:      map[string]*models.ModelPricing{},
	}

	result, decision := h.evaluator.Evaluate(ctx, policies, candidates)

	c.JSON(http.StatusOK, gin.H{
		"candidates": result,
		"decision":   decision,
		"policies":   len(policies),
	})
}

// buildCandidates creates routing candidates from all active provider configs.
func (h *RoutingAdminHandler) buildCandidates(c *gin.Context) ([]routing.Candidate, error) {
	if h.stores.Providers == nil {
		return nil, nil
	}

	// For dry-run, list all global configs (admin scope).
	configs, err := h.stores.Providers.ListGlobal(context.Background())
	if err != nil {
		return nil, err
	}

	candidates := make([]routing.Candidate, 0, len(configs))
	for _, cfg := range configs {
		if !cfg.IsActive {
			continue
		}
		candidates = append(candidates, routing.Candidate{
			ConfigID:   cfg.ID,
			ProviderID: cfg.Provider,
			Model:      "", // model is applied per-policy or from request
			Endpoint:   cfg.Endpoint,
		})
	}
	return candidates, nil
}
