// Package handlers provides HTTP handlers for the rdev API.
package handlers

import (
	"net/http"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/orchard9/rdev/internal/auth"
	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/port"
	"github.com/orchard9/rdev/pkg/api"
)

// AgentsHandler handles code agent management endpoints.
type AgentsHandler struct {
	registry port.CodeAgentRegistry
}

// NewAgentsHandler creates a new agents handler.
func NewAgentsHandler(registry port.CodeAgentRegistry) *AgentsHandler {
	return &AgentsHandler{
		registry: registry,
	}
}

// Mount registers the agent routes.
func (h *AgentsHandler) Mount(r api.Router) {
	r.Route("/agents", func(r chi.Router) {
		// Read operations
		r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).Get("/", h.List)
		r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).Get("/health", h.Health)
		r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).Get("/{provider}", h.GetCapabilities)

		// Write operations
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).Post("/default", h.SetDefault)
	})
}

// AgentDTO is the data transfer object for code agents.
type AgentDTO struct {
	Provider     string   `json:"provider"`
	Name         string   `json:"name"`
	Available    bool     `json:"available"`
	Default      bool     `json:"default"`
	Models       []string `json:"supported_models,omitempty"`
	DefaultModel string   `json:"default_model,omitempty"`
}

// AgentCapabilitiesDTO is the DTO for agent capabilities.
type AgentCapabilitiesDTO struct {
	Provider                    string   `json:"provider"`
	SupportsSessionContinuation bool     `json:"supports_session_continuation"`
	SupportsModelSelection      bool     `json:"supports_model_selection"`
	SupportsToolControl         bool     `json:"supports_tool_control"`
	SupportsStreaming           bool     `json:"supports_streaming"`
	SupportedModels             []string `json:"supported_models"`
	DefaultModel                string   `json:"default_model"`
	MaxPromptLength             int      `json:"max_prompt_length,omitempty"`
}

// ListAgentsResponse is the response for GET /agents.
type ListAgentsResponse struct {
	Agents         []AgentDTO `json:"agents"`
	DefaultAgent   string     `json:"default_agent"`
	TotalAgents    int        `json:"total_agents"`
	AvailableCount int        `json:"available_count"`
}

// List returns all registered code agents and their status.
// GET /agents
func (h *AgentsHandler) List(w http.ResponseWriter, r *http.Request) {
	if h.registry == nil {
		api.WriteSuccess(w, r, ListAgentsResponse{
			Agents:         []AgentDTO{},
			DefaultAgent:   "",
			TotalAgents:    0,
			AvailableCount: 0,
		})
		return
	}

	providers := h.registry.Available()
	defaultProvider := h.registry.DefaultProvider()

	// Check availability for each agent
	availableAgents := h.registry.AvailableAgents(r.Context())
	availableSet := make(map[domain.AgentProvider]bool)
	for _, agent := range availableAgents {
		availableSet[agent.Provider()] = true
	}

	agents := make([]AgentDTO, 0, len(providers))
	availableCount := 0

	for _, provider := range providers {
		agent := h.registry.Get(provider)
		if agent == nil {
			continue
		}

		caps := agent.Capabilities()
		isAvailable := availableSet[provider]
		isDefault := provider == defaultProvider

		if isAvailable {
			availableCount++
		}

		agents = append(agents, AgentDTO{
			Provider:     string(provider),
			Name:         agent.Name(),
			Available:    isAvailable,
			Default:      isDefault,
			Models:       caps.SupportedModels,
			DefaultModel: caps.DefaultModel,
		})
	}

	api.WriteSuccess(w, r, ListAgentsResponse{
		Agents:         agents,
		DefaultAgent:   string(defaultProvider),
		TotalAgents:    len(agents),
		AvailableCount: availableCount,
	})
}

// GetCapabilities returns the capabilities of a specific agent.
// GET /agents/{provider}
func (h *AgentsHandler) GetCapabilities(w http.ResponseWriter, r *http.Request) {
	providerStr := chi.URLParam(r, "provider")
	provider := domain.AgentProvider(providerStr)

	if h.registry == nil {
		api.WriteNotFound(w, r, "no agents registered")
		return
	}

	agent := h.registry.Get(provider)
	if agent == nil {
		api.WriteNotFound(w, r, "agent not found: "+providerStr)
		return
	}

	caps := agent.Capabilities()

	api.WriteSuccess(w, r, AgentCapabilitiesDTO{
		Provider:                    string(caps.Provider),
		SupportsSessionContinuation: caps.SupportsSessionContinuation,
		SupportsModelSelection:      caps.SupportsModelSelection,
		SupportsToolControl:         caps.SupportsToolControl,
		SupportsStreaming:           caps.SupportsStreaming,
		SupportedModels:             caps.SupportedModels,
		DefaultModel:                caps.DefaultModel,
		MaxPromptLength:             caps.MaxPromptLength,
	})
}

// AgentHealthDTO represents the health status of a single agent.
type AgentHealthDTO struct {
	Provider  string `json:"provider"`
	Name      string `json:"name"`
	Healthy   bool   `json:"healthy"`
	Message   string `json:"message"`
	Latency   string `json:"latency"`
	CheckedAt string `json:"checked_at"`
}

// AgentHealthResponse is the response for GET /agents/health.
type AgentHealthResponse struct {
	Agents        []AgentHealthDTO `json:"agents"`
	HealthyCount  int              `json:"healthy_count"`
	TotalCount    int              `json:"total_count"`
	DefaultAgent  string           `json:"default_agent"`
	DefaultHealth bool             `json:"default_healthy"`
}

// Health returns the health status of all registered code agents.
// GET /agents/health
func (h *AgentsHandler) Health(w http.ResponseWriter, r *http.Request) {
	if h.registry == nil {
		api.WriteSuccess(w, r, AgentHealthResponse{
			Agents:       []AgentHealthDTO{},
			HealthyCount: 0,
			TotalCount:   0,
		})
		return
	}

	providers := h.registry.Available()
	defaultProvider := h.registry.DefaultProvider()

	agents := make([]AgentHealthDTO, 0, len(providers))
	healthyCount := 0
	defaultHealthy := false

	for _, provider := range providers {
		agent := h.registry.Get(provider)
		if agent == nil {
			continue
		}

		start := time.Now()
		healthy := agent.Available(r.Context())
		latency := time.Since(start)

		msg := "available"
		if !healthy {
			msg = "unavailable"
		}

		if healthy {
			healthyCount++
		}
		if provider == defaultProvider {
			defaultHealthy = healthy
		}

		agents = append(agents, AgentHealthDTO{
			Provider:  string(provider),
			Name:      agent.Name(),
			Healthy:   healthy,
			Message:   msg,
			Latency:   latency.String(),
			CheckedAt: time.Now().UTC().Format(time.RFC3339),
		})
	}

	api.WriteSuccess(w, r, AgentHealthResponse{
		Agents:        agents,
		HealthyCount:  healthyCount,
		TotalCount:    len(agents),
		DefaultAgent:  string(defaultProvider),
		DefaultHealth: defaultHealthy,
	})
}

// SetDefaultRequest is the request body for POST /agents/default.
type SetDefaultRequest struct {
	Provider string `json:"provider"`
}

// SetDefault changes the default code agent.
// POST /agents/default
func (h *AgentsHandler) SetDefault(w http.ResponseWriter, r *http.Request) {
	if h.registry == nil {
		api.WriteBadRequest(w, r, "no agents registered")
		return
	}

	var req SetDefaultRequest
	if err := api.DecodeJSON(r, &req); err != nil {
		api.WriteBadRequest(w, r, "invalid request body")
		return
	}

	if req.Provider == "" {
		api.WriteBadRequest(w, r, "provider is required")
		return
	}

	provider := domain.AgentProvider(req.Provider)
	if err := h.registry.SetDefault(provider); err != nil {
		api.WriteBadRequest(w, r, err.Error())
		return
	}

	api.WriteSuccess(w, r, map[string]any{
		"default_agent": req.Provider,
		"message":       "default agent updated",
	})
}
