package server

import (
	"database/sql"
	"net/http"
	"strconv"
	"strings"

	"github.com/go-chi/chi/v5"

	"github.com/operator/command-center/internal/integrations/autobrr"
)

// AutomationDeps bundles the Phase 5 dependencies.
type AutomationDeps struct {
	// Clients keyed by automation_tool id. Only autobrr in Phase 5.
	Clients map[string]*autobrr.Client
}

type automationToolDTO struct {
	ID                  string `json:"id"`
	Name                string `json:"name"`
	Type                string `json:"type"`
	BaseURL             string `json:"base_url"`
	Enabled             bool   `json:"enabled"`
	PollIntervalSeconds int    `json:"poll_interval_seconds"`
	Healthy             *bool  `json:"healthy,omitempty"`
}

type filterDTO struct {
	ToolID             string  `json:"tool_id"`
	FilterExternalID   string  `json:"filter_external_id"`
	FilterName         string  `json:"filter_name"`
	Enabled            bool    `json:"enabled"`
	GrabCount          int64   `json:"grab_count"`
	LinkedCount        int64   `json:"linked_count"`
	TotalUploadedBytes int64   `json:"total_uploaded_bytes"`
	MedianUploadedBytes int64  `json:"median_uploaded_bytes,omitempty"`
	AvgRatio           float64 `json:"avg_ratio,omitempty"`
	Score              float64 `json:"score,omitempty"`
}

func (s *Server) handleListAutomationTools(w http.ResponseWriter, r *http.Request) {
	rows, err := s.sqlite.QueryContext(r.Context(), `
		SELECT id, name, type, base_url, enabled, poll_interval_seconds
		FROM automation_tools ORDER BY name
	`)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer rows.Close()
	out := []automationToolDTO{}
	for rows.Next() {
		var (
			d       automationToolDTO
			enabled int
		)
		if err := rows.Scan(&d.ID, &d.Name, &d.Type, &d.BaseURL, &enabled, &d.PollIntervalSeconds); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		d.Enabled = enabled != 0
		out = append(out, d)
	}
	writeJSON(w, http.StatusOK, map[string]any{"count": len(out), "tools": out})
}

func (s *Server) handleAutomationToolStatus(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")
	if id == "" {
		http.Error(w, "missing id", http.StatusBadRequest)
		return
	}
	if s.automation == nil {
		http.Error(w, "automation not configured", http.StatusServiceUnavailable)
		return
	}
	client, ok := s.automation.Clients[id]
	if !ok {
		http.Error(w, "tool not found", http.StatusNotFound)
		return
	}
	statuses, err := client.IndexerStatuses(r.Context())
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadGateway)
		return
	}
	healthErr := client.Health(r.Context())
	writeJSON(w, http.StatusOK, map[string]any{
		"id":        id,
		"healthy":   healthErr == nil,
		"indexers":  statuses,
	})
}

func (s *Server) handleListFilters(w http.ResponseWriter, r *http.Request) {
	rows, err := s.sqlite.QueryContext(r.Context(), `
		SELECT automation_tool_id, filter_external_id, filter_name,
		       COUNT(*) AS grab_count,
		       COUNT(info_hash) AS linked_count,
		       COALESCE(SUM(final_uploaded_bytes), 0) AS total_up,
		       COALESCE(AVG(final_ratio), 0) AS avg_ratio
		FROM filter_performance
		GROUP BY automation_tool_id, filter_external_id, filter_name
		ORDER BY total_up DESC
	`)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer rows.Close()
	out := []filterDTO{}
	for rows.Next() {
		var d filterDTO
		if err := rows.Scan(&d.ToolID, &d.FilterExternalID, &d.FilterName,
			&d.GrabCount, &d.LinkedCount, &d.TotalUploadedBytes, &d.AvgRatio); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		// Score: total uploaded per grab, normalized. Documented in
		// DECISIONS.md D30; intentionally simple for Phase 5.
		if d.GrabCount > 0 {
			d.Score = float64(d.TotalUploadedBytes) / float64(d.GrabCount)
		}
		out = append(out, d)
	}
	writeJSON(w, http.StatusOK, map[string]any{"count": len(out), "filters": out})
}

type filterDetailDTO struct {
	filterDTO
	RecentGrabs []recentGrabDTO `json:"recent_grabs"`
}

type recentGrabDTO struct {
	ID                 int64    `json:"id"`
	ReleaseName        string   `json:"release_name"`
	InfoHash           string   `json:"info_hash,omitempty"`
	GrabbedAt          int64    `json:"grabbed_at"`
	FinalUploadedBytes *int64   `json:"final_uploaded_bytes,omitempty"`
	FinalRatio         *float64 `json:"final_ratio,omitempty"`
	LastMeasuredAt     *int64   `json:"last_measured_at,omitempty"`
}

func (s *Server) handleGetFilter(w http.ResponseWriter, r *http.Request) {
	toolID := r.URL.Query().Get("tool_id")
	if toolID == "" {
		toolID = "autobrr"
	}
	id := chi.URLParam(r, "id")
	if id == "" {
		http.Error(w, "missing id", http.StatusBadRequest)
		return
	}

	// Aggregate.
	row := s.sqlite.QueryRowContext(r.Context(), `
		SELECT automation_tool_id, filter_external_id, filter_name,
		       COUNT(*) AS grab_count,
		       COUNT(info_hash) AS linked_count,
		       COALESCE(SUM(final_uploaded_bytes), 0) AS total_up,
		       COALESCE(AVG(final_ratio), 0) AS avg_ratio
		FROM filter_performance
		WHERE automation_tool_id = ? AND filter_external_id = ?
		GROUP BY automation_tool_id, filter_external_id, filter_name
	`, toolID, id)
	var d filterDTO
	if err := row.Scan(&d.ToolID, &d.FilterExternalID, &d.FilterName,
		&d.GrabCount, &d.LinkedCount, &d.TotalUploadedBytes, &d.AvgRatio); err != nil {
		if err == sql.ErrNoRows {
			http.Error(w, "filter not found", http.StatusNotFound)
			return
		}
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if d.GrabCount > 0 {
		d.Score = float64(d.TotalUploadedBytes) / float64(d.GrabCount)
	}

	// Recent grabs.
	limit := 50
	if v := r.URL.Query().Get("limit"); v != "" {
		if n, _ := strconv.Atoi(v); n > 0 && n <= 500 {
			limit = n
		}
	}
	rows, err := s.sqlite.QueryContext(r.Context(), `
		SELECT id, release_name, info_hash, grabbed_at,
		       final_uploaded_bytes, final_ratio, last_measured_at
		FROM filter_performance
		WHERE automation_tool_id = ? AND filter_external_id = ?
		ORDER BY grabbed_at DESC LIMIT ?
	`, toolID, id, limit)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer rows.Close()
	grabs := []recentGrabDTO{}
	for rows.Next() {
		var (
			g                            recentGrabDTO
			release, hash                sql.NullString
			finalUp                      sql.NullInt64
			finalRatio                   sql.NullFloat64
			lastMeasured                 sql.NullInt64
		)
		if err := rows.Scan(&g.ID, &release, &hash, &g.GrabbedAt, &finalUp, &finalRatio, &lastMeasured); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		if release.Valid {
			g.ReleaseName = release.String
		}
		if hash.Valid {
			g.InfoHash = hash.String
		}
		g.FinalUploadedBytes = nullableInt(finalUp)
		g.FinalRatio = nullableFloat(finalRatio)
		g.LastMeasuredAt = nullableInt(lastMeasured)
		grabs = append(grabs, g)
	}
	writeJSON(w, http.StatusOK, filterDetailDTO{filterDTO: d, RecentGrabs: grabs})
}

func (s *Server) handleRecentReleases(w http.ResponseWriter, r *http.Request) {
	toolID := r.URL.Query().Get("tool_id")
	if toolID == "" {
		toolID = "autobrr"
	}
	if s.automation == nil {
		http.Error(w, "automation not configured", http.StatusServiceUnavailable)
		return
	}
	client, ok := s.automation.Clients[toolID]
	if !ok {
		http.Error(w, "tool not found", http.StatusNotFound)
		return
	}
	limit := 100
	if v := r.URL.Query().Get("limit"); v != "" {
		if n, _ := strconv.Atoi(v); n > 0 {
			limit = n
		}
	}
	rs, err := client.RecentReleases(r.Context(), limit)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadGateway)
		return
	}
	// Optional client-side filter by filter id.
	if f := r.URL.Query().Get("filter"); f != "" {
		filtered := rs[:0]
		for _, rec := range rs {
			if strconv.Itoa(rec.FilterID) == f ||
				strings.EqualFold(rec.FilterName, f) {
				filtered = append(filtered, rec)
			}
		}
		rs = filtered
	}
	writeJSON(w, http.StatusOK, map[string]any{
		"tool_id":  toolID,
		"count":    len(rs),
		"releases": rs,
	})
}
