package http

import (
	"database/sql"
	"fmt"
	"log/slog"
	"net/http"
	"time"

	"github.com/google/uuid"

	"github.com/nextlevelbuilder/goclaw/internal/store"
)

// UsageHandler serves pre-computed usage analytics from snapshots.
type UsageHandler struct {
	snapshots store.SnapshotStore
	db        *sql.DB
	token     string
}

func NewUsageHandler(snapshots store.SnapshotStore, db *sql.DB, token string) *UsageHandler {
	return &UsageHandler{snapshots: snapshots, db: db, token: token}
}

func (h *UsageHandler) RegisterRoutes(mux *http.ServeMux) {
	mux.HandleFunc("GET /v1/usage/timeseries", h.authMiddleware(h.handleTimeSeries))
	mux.HandleFunc("GET /v1/usage/breakdown", h.authMiddleware(h.handleBreakdown))
	mux.HandleFunc("GET /v1/usage/summary", h.authMiddleware(h.handleSummary))
}

func (h *UsageHandler) authMiddleware(next http.HandlerFunc) http.HandlerFunc {
	return requireAuth(h.token, "", next)
}

func (h *UsageHandler) handleTimeSeries(w http.ResponseWriter, r *http.Request) {
	q := parseSnapshotFilters(r)
	if q.From.IsZero() || q.To.IsZero() {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "from and to are required"})
		return
	}
	if q.GroupBy == "" {
		q.GroupBy = "hour"
	}

	points, err := h.snapshots.GetTimeSeries(r.Context(), q)
	if err != nil {
		slog.Error("usage.timeseries query failed", "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "internal server error"})
		return
	}

	// Gap-fill: if "to" extends into current incomplete hour, query live traces
	now := time.Now().UTC()
	currentHourStart := now.Truncate(time.Hour)
	if q.To.After(currentHourStart) && currentHourStart.After(q.From) {
		livePoint := h.queryLiveHour(r, currentHourStart, now, q)
		if livePoint != nil {
			points = append(points, *livePoint)
		}
	}

	writeJSON(w, http.StatusOK, map[string]any{"points": points})
}

func (h *UsageHandler) handleBreakdown(w http.ResponseWriter, r *http.Request) {
	q := parseSnapshotFilters(r)
	if q.From.IsZero() || q.To.IsZero() {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "from and to are required"})
		return
	}
	if q.GroupBy == "" {
		q.GroupBy = "provider"
	}

	rows, err := h.snapshots.GetBreakdown(r.Context(), q)
	if err != nil {
		slog.Error("usage.breakdown query failed", "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "internal server error"})
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"rows": rows})
}

func (h *UsageHandler) handleSummary(w http.ResponseWriter, r *http.Request) {
	period := r.URL.Query().Get("period")
	if period == "" {
		period = "24h"
	}

	now := time.Now().UTC()
	var currentFrom, previousFrom time.Time

	switch period {
	case "today":
		currentFrom = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)
		previousFrom = currentFrom.AddDate(0, 0, -1)
	case "7d":
		currentFrom = now.Add(-7 * 24 * time.Hour)
		previousFrom = currentFrom.Add(-7 * 24 * time.Hour)
	case "30d":
		currentFrom = now.Add(-30 * 24 * time.Hour)
		previousFrom = currentFrom.Add(-30 * 24 * time.Hour)
	default: // "24h"
		currentFrom = now.Add(-24 * time.Hour)
		previousFrom = currentFrom.Add(-24 * time.Hour)
	}

	baseQ := parseSnapshotFilters(r)

	// Current period
	currentQ := baseQ
	currentQ.From = currentFrom
	currentQ.To = now
	currentQ.GroupBy = "hour"

	// Previous period (same duration, shifted back)
	previousQ := baseQ
	previousQ.From = previousFrom
	previousQ.To = currentFrom
	previousQ.GroupBy = "hour"

	currentSummary := h.aggregateTimeSeries(r, currentQ)
	previousSummary := h.aggregateTimeSeries(r, previousQ)

	writeJSON(w, http.StatusOK, map[string]any{
		"current":  currentSummary,
		"previous": previousSummary,
	})
}

// usageSummary is the response shape for summary endpoint.
type usageSummary struct {
	Requests      int     `json:"requests"`
	InputTokens   int64   `json:"input_tokens"`
	OutputTokens  int64   `json:"output_tokens"`
	Cost          float64 `json:"cost"`
	UniqueUsers   int     `json:"unique_users"`
	Errors        int     `json:"errors"`
	LLMCalls      int     `json:"llm_calls"`
	ToolCalls     int     `json:"tool_calls"`
	AvgDurationMS int     `json:"avg_duration_ms"`
}

func (h *UsageHandler) aggregateTimeSeries(r *http.Request, q store.SnapshotQuery) usageSummary {
	points, err := h.snapshots.GetTimeSeries(r.Context(), q)
	if err != nil {
		return usageSummary{}
	}

	var s usageSummary
	var totalWeightedDuration int64
	for _, p := range points {
		s.Requests += p.RequestCount
		s.InputTokens += p.InputTokens
		s.OutputTokens += p.OutputTokens
		s.Cost += p.TotalCost
		s.UniqueUsers += p.UniqueUsers
		s.Errors += p.ErrorCount
		s.LLMCalls += p.LLMCallCount
		s.ToolCalls += p.ToolCallCount
		totalWeightedDuration += int64(p.AvgDurationMS) * int64(p.RequestCount)
	}
	if s.Requests > 0 {
		s.AvgDurationMS = int(totalWeightedDuration / int64(s.Requests))
	}
	return s
}

// queryLiveHour runs a lightweight query on traces for the current incomplete hour.
func (h *UsageHandler) queryLiveHour(r *http.Request, from, to time.Time, q store.SnapshotQuery) *store.SnapshotTimeSeries {
	query := `SELECT
		COUNT(*),
		COUNT(*) FILTER (WHERE status = 'error'),
		COUNT(DISTINCT user_id),
		COALESCE(SUM(total_input_tokens), 0),
		COALESCE(SUM(total_output_tokens), 0),
		COALESCE(SUM(total_cost), 0),
		COALESCE(SUM(llm_call_count), 0),
		COALESCE(SUM(tool_call_count), 0),
		COALESCE(AVG(duration_ms), 0)::INTEGER
	FROM traces
	WHERE start_time >= $1 AND start_time < $2
	  AND parent_trace_id IS NULL`

	args := []any{from, to}
	idx := 3
	if q.AgentID != nil {
		query += fmt.Sprintf(" AND agent_id = $%d", idx)
		args = append(args, *q.AgentID)
		idx++
	}
	if q.Channel != "" {
		query += fmt.Sprintf(" AND channel = $%d", idx)
		args = append(args, q.Channel)
		idx++
	}
	// Note: provider/model filters are not applied here because the traces table
	// does not have provider/model columns (those live on spans). The live gap-fill
	// is a rough approximation for the current incomplete hour.

	var p store.SnapshotTimeSeries
	p.BucketTime = from
	err := h.db.QueryRowContext(r.Context(), query, args...).Scan(
		&p.RequestCount, &p.ErrorCount, &p.UniqueUsers,
		&p.InputTokens, &p.OutputTokens, &p.TotalCost,
		&p.LLMCallCount, &p.ToolCallCount, &p.AvgDurationMS,
	)
	if err != nil || p.RequestCount == 0 {
		return nil
	}
	return &p
}

func parseSnapshotFilters(r *http.Request) store.SnapshotQuery {
	q := store.SnapshotQuery{}
	if v := r.URL.Query().Get("from"); v != "" {
		if t, err := time.Parse(time.RFC3339, v); err == nil {
			q.From = t
		}
	}
	if v := r.URL.Query().Get("to"); v != "" {
		if t, err := time.Parse(time.RFC3339, v); err == nil {
			q.To = t
		}
	}
	if v := r.URL.Query().Get("agent_id"); v != "" {
		if id, err := uuid.Parse(v); err == nil {
			q.AgentID = &id
		}
	}
	q.Provider = r.URL.Query().Get("provider")
	q.Model = r.URL.Query().Get("model")
	q.Channel = r.URL.Query().Get("channel")
	q.GroupBy = r.URL.Query().Get("group_by")
	return q
}

