package main

import (
	"net/http"
	"sort"
	"strings"
	"time"

	"fn-registry/functions/core"
	"fn-registry/functions/datascience"
	"fn-registry/functions/infra"
)

type DurationStats = datascience.DurationStats

type Metrics struct {
	Range            DateRange         `json:"range"`
	Totals           Totals            `json:"totals"`
	ByColumn         []ColumnCount     `json:"by_column"`
	ThroughputDaily  []DailyCount      `json:"throughput_daily"`
	CreatedDaily     []DailyCount      `json:"created_daily"`
	LeadTime         DurationStats     `json:"lead_time"`
	CycleTimeColumn  []ColumnDuration  `json:"cycle_time_per_column"`
	TopAssignees     []AssigneeStat    `json:"top_assignees"`
	TopRequesters    []RequesterStat   `json:"top_requesters"`
	MovementsByUser  []MovementStat    `json:"movements_by_user"`
	LockTotalMs      int64             `json:"lock_total_ms"`
	LockActiveCount  int               `json:"lock_active_count"`
	CumulativeFlow   []CumulativePoint `json:"cumulative_flow"`
}

type CumulativePoint struct {
	Date  string `json:"date"`
	Total int    `json:"total"`
	Done  int    `json:"done"`
}

type DateRange struct {
	From string `json:"from"`
	To   string `json:"to"`
}

type Totals struct {
	Cards          int `json:"cards"`
	CardsCompleted int `json:"cards_completed_in_range"`
	CardsCreated   int `json:"cards_created_in_range"`
	CardsActive    int `json:"cards_active"`
	CardsDone      int `json:"cards_done"`
	Columns        int `json:"columns"`
	Users          int `json:"users"`
	ActiveLocks    int `json:"active_locks"`
}

type ColumnCount struct {
	ColumnID string `json:"column_id"`
	Name     string `json:"name"`
	IsDone   bool   `json:"is_done"`
	Count    int    `json:"count"`
}

type DailyCount struct {
	Date  string `json:"date"`
	Count int    `json:"count"`
}

type ColumnDuration struct {
	ColumnID string        `json:"column_id"`
	Name     string        `json:"name"`
	IsDone   bool          `json:"is_done"`
	Stats    DurationStats `json:"stats"`
}

type AssigneeStat struct {
	UserID      string `json:"user_id"`
	Username    string `json:"username"`
	DisplayName string `json:"display_name"`
	Active      int    `json:"active"`
	Completed   int    `json:"completed_in_range"`
}

type RequesterStat struct {
	Requester string `json:"requester"`
	Total     int    `json:"total"`
	Active    int    `json:"active"`
	Completed int    `json:"completed_in_range"`
}

type MovementStat struct {
	UserID      string `json:"user_id"`
	Username    string `json:"username"`
	DisplayName string `json:"display_name"`
	Moves       int    `json:"moves"`
}

func computeStats(durations []int64) DurationStats {
	return datascience.DurationStatsFrom(durations)
}

func parseDateOrDefault(s string, dflt time.Time) time.Time {
	return core.ParseDateOrDefault(s, dflt, false)
}

func parseEndDateOrDefault(s string, dflt time.Time) time.Time {
	return core.ParseDateOrDefault(s, dflt, true)
}

// GET /api/metrics?from=YYYY-MM-DD&to=YYYY-MM-DD&assignee_id=...&requester=...
func handleMetrics(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		now := time.Now().UTC()
		from := parseDateOrDefault(r.URL.Query().Get("from"), now.AddDate(0, 0, -30))
		to := parseEndDateOrDefault(r.URL.Query().Get("to"), now)
		assignee := r.URL.Query().Get("assignee_id")
		requester := r.URL.Query().Get("requester")
		tagsRaw := r.URL.Query().Get("tags")
		var tags []string
		if tagsRaw != "" {
			for _, t := range strings.Split(tagsRaw, ",") {
				if t = strings.TrimSpace(t); t != "" {
					tags = append(tags, t)
				}
			}
		}

		m, err := computeMetrics(db, from, to, assignee, requester, tags)
		if err != nil {
			serverError(w, err)
			return
		}
		infra.HTTPJSONResponse(w, http.StatusOK, m)
	}
}

func computeMetrics(db *DB, from, to time.Time, assignee, requester string, tags []string) (*Metrics, error) {
	fromStr := from.Format(time.RFC3339Nano)
	toStr := to.Format(time.RFC3339Nano)

	m := &Metrics{
		Range:            DateRange{From: from.Format("2006-01-02"), To: to.Format("2006-01-02")},
		ByColumn:         []ColumnCount{},
		ThroughputDaily:  []DailyCount{},
		CreatedDaily:     []DailyCount{},
		CycleTimeColumn:  []ColumnDuration{},
		TopAssignees:     []AssigneeStat{},
		TopRequesters:    []RequesterStat{},
		MovementsByUser:  []MovementStat{},
		CumulativeFlow:   []CumulativePoint{},
	}

	cardWhere := "WHERE deleted_at IS NULL"
	args := []any{}
	if assignee != "" {
		cardWhere += " AND assignee_id=?"
		args = append(args, assignee)
	}
	if requester != "" {
		cardWhere += " AND requester=?"
		args = append(args, requester)
	}
	for _, t := range tags {
		cardWhere += " AND tags LIKE ?"
		args = append(args, `%"`+t+`"%`)
	}

	if err := db.conn.QueryRow(`SELECT COUNT(*) FROM cards `+cardWhere, args...).Scan(&m.Totals.Cards); err != nil {
		return nil, err
	}
	completedArgs := append([]any{fromStr, toStr}, args...)
	if err := db.conn.QueryRow(
		`SELECT COUNT(*) FROM cards `+cardWhere+` AND completed_at IS NOT NULL AND completed_at>=? AND completed_at<=?`,
		append(args, fromStr, toStr)...,
	).Scan(&m.Totals.CardsCompleted); err != nil {
		return nil, err
	}
	if err := db.conn.QueryRow(
		`SELECT COUNT(*) FROM cards `+cardWhere+` AND created_at>=? AND created_at<=?`,
		append(args, fromStr, toStr)...,
	).Scan(&m.Totals.CardsCreated); err != nil {
		return nil, err
	}
	if err := db.conn.QueryRow(
		`SELECT COUNT(*) FROM cards `+cardWhere+` AND (completed_at IS NULL OR completed_at='')`,
		args...,
	).Scan(&m.Totals.CardsActive); err != nil {
		return nil, err
	}
	if err := db.conn.QueryRow(
		`SELECT COUNT(*) FROM cards `+cardWhere+` AND completed_at IS NOT NULL AND completed_at!=''`,
		args...,
	).Scan(&m.Totals.CardsDone); err != nil {
		return nil, err
	}
	_ = completedArgs

	if err := db.conn.QueryRow(`SELECT COUNT(*) FROM columns`).Scan(&m.Totals.Columns); err != nil {
		return nil, err
	}
	if err := db.conn.QueryRow(`SELECT COUNT(*) FROM users`).Scan(&m.Totals.Users); err != nil {
		return nil, err
	}
	lockActiveQ := `SELECT COUNT(*) FROM card_lock_history h JOIN cards c ON c.id=h.card_id WHERE h.unlocked_at IS NULL AND c.deleted_at IS NULL`
	if assignee != "" {
		lockActiveQ += ` AND c.assignee_id=?`
	}
	if requester != "" {
		lockActiveQ += ` AND c.requester=?`
	}
	if err := db.conn.QueryRow(lockActiveQ, args...).Scan(&m.Totals.ActiveLocks); err != nil {
		return nil, err
	}

	// By column.
	rows, err := db.conn.Query(
		`SELECT col.id, col.name, col.is_done, COUNT(c.id)
		 FROM columns col
		 LEFT JOIN cards c ON c.column_id=col.id`+
			condFromCard(assignee, requester, "c", "WHERE")+
			` GROUP BY col.id ORDER BY col.position`,
		colArgs(assignee, requester)...,
	)
	if err != nil {
		return nil, err
	}
	for rows.Next() {
		var cc ColumnCount
		var isDone int
		if err := rows.Scan(&cc.ColumnID, &cc.Name, &isDone, &cc.Count); err != nil {
			rows.Close()
			return nil, err
		}
		cc.IsDone = isDone != 0
		m.ByColumn = append(m.ByColumn, cc)
	}
	rows.Close()

	// Throughput daily (completed_at within range).
	m.ThroughputDaily, err = dailyBucket(db, "completed_at", fromStr, toStr, assignee, requester, true)
	if err != nil {
		return nil, err
	}
	m.CreatedDaily, err = dailyBucket(db, "created_at", fromStr, toStr, assignee, requester, false)
	if err != nil {
		return nil, err
	}

	// Lead time (cards completed in range, completed_at - created_at).
	leadDurs, err := collectDurations(db,
		`SELECT (julianday(completed_at) - julianday(created_at)) * 86400000 FROM cards `+
			cardWhere+` AND completed_at IS NOT NULL AND completed_at>=? AND completed_at<=?`,
		append(args, fromStr, toStr)...,
	)
	if err != nil {
		return nil, err
	}
	m.LeadTime = computeStats(leadDurs)

	// Cycle time per column.
	colRows, err := db.conn.Query(`SELECT id, name, is_done FROM columns ORDER BY position`)
	if err != nil {
		return nil, err
	}
	type colInfo struct {
		id, name string
		isDone   bool
	}
	var cols []colInfo
	for colRows.Next() {
		var ci colInfo
		var d int
		if err := colRows.Scan(&ci.id, &ci.name, &d); err != nil {
			colRows.Close()
			return nil, err
		}
		ci.isDone = d != 0
		cols = append(cols, ci)
	}
	colRows.Close()

	now := time.Now().UTC()
	cap := to
	if now.Before(cap) {
		cap = now
	}
	capStr := cap.Format(time.RFC3339Nano)
	for _, ci := range cols {
		histArgs := []any{ci.id, fromStr, toStr}
		histQ := `SELECT (julianday(COALESCE(h.exited_at, ?)) - julianday(h.entered_at)) * 86400000
		           FROM card_column_history h JOIN cards c ON c.id=h.card_id
		           WHERE h.column_id=? AND h.entered_at>=? AND h.entered_at<=?`
		histArgs = append([]any{capStr}, histArgs...)
		if assignee != "" {
			histQ += ` AND c.assignee_id=?`
			histArgs = append(histArgs, assignee)
		}
		if requester != "" {
			histQ += ` AND c.requester=?`
			histArgs = append(histArgs, requester)
		}
		durs, err := collectDurations(db, histQ, histArgs...)
		if err != nil {
			return nil, err
		}
		m.CycleTimeColumn = append(m.CycleTimeColumn, ColumnDuration{
			ColumnID: ci.id, Name: ci.name, IsDone: ci.isDone,
			Stats: computeStats(durs),
		})
	}

	// Top assignees.
	asRows, err := db.conn.Query(
		`SELECT u.id, u.username, u.display_name,
		        SUM(CASE WHEN c.completed_at IS NULL OR c.completed_at='' THEN 1 ELSE 0 END) as active,
		        SUM(CASE WHEN c.completed_at IS NOT NULL AND c.completed_at>=? AND c.completed_at<=? THEN 1 ELSE 0 END) as completed
		 FROM users u
		 LEFT JOIN cards c ON c.assignee_id=u.id` + cardJoinFilter(requester) +
			` GROUP BY u.id ORDER BY completed DESC, active DESC`,
		topAssigneeArgs(fromStr, toStr, requester)...,
	)
	if err != nil {
		return nil, err
	}
	for asRows.Next() {
		var s AssigneeStat
		if err := asRows.Scan(&s.UserID, &s.Username, &s.DisplayName, &s.Active, &s.Completed); err != nil {
			asRows.Close()
			return nil, err
		}
		m.TopAssignees = append(m.TopAssignees, s)
	}
	asRows.Close()

	// Top requesters.
	reqRows, err := db.conn.Query(
		`SELECT requester,
		        COUNT(*) as total,
		        SUM(CASE WHEN completed_at IS NULL OR completed_at='' THEN 1 ELSE 0 END) as active,
		        SUM(CASE WHEN completed_at IS NOT NULL AND completed_at>=? AND completed_at<=? THEN 1 ELSE 0 END) as completed
		 FROM cards
		 WHERE deleted_at IS NULL AND requester != ''`+
			condFromCard(assignee, "", "", "AND")+
			` GROUP BY requester ORDER BY total DESC LIMIT 10`,
		topReqArgs(fromStr, toStr, assignee)...,
	)
	if err != nil {
		return nil, err
	}
	for reqRows.Next() {
		var s RequesterStat
		if err := reqRows.Scan(&s.Requester, &s.Total, &s.Active, &s.Completed); err != nil {
			reqRows.Close()
			return nil, err
		}
		m.TopRequesters = append(m.TopRequesters, s)
	}
	reqRows.Close()

	// Movements by user.
	mvRows, err := db.conn.Query(
		`SELECT u.id, u.username, u.display_name, COUNT(h.id) as moves
		 FROM users u
		 LEFT JOIN card_column_history h ON h.actor_id=u.id AND h.entered_at>=? AND h.entered_at<=?
		 GROUP BY u.id ORDER BY moves DESC`,
		fromStr, toStr,
	)
	if err != nil {
		return nil, err
	}
	for mvRows.Next() {
		var s MovementStat
		if err := mvRows.Scan(&s.UserID, &s.Username, &s.DisplayName, &s.Moves); err != nil {
			mvRows.Close()
			return nil, err
		}
		m.MovementsByUser = append(m.MovementsByUser, s)
	}
	mvRows.Close()

	// Lock total in range.
	var lockMs float64
	if err := db.conn.QueryRow(
		`SELECT COALESCE(SUM(
		   (julianday(COALESCE(h.unlocked_at, ?)) - julianday(h.locked_at)) * 86400000
		 ), 0) FROM card_lock_history h JOIN cards c ON c.id=h.card_id
		 WHERE h.locked_at>=? AND h.locked_at<=?`+condFromCard(assignee, requester, "c", "AND"),
		append([]any{toStr, fromStr, toStr}, colArgs(assignee, requester)...)...,
	).Scan(&lockMs); err != nil {
		return nil, err
	}
	m.LockTotalMs = int64(lockMs)

	// Cumulative flow: walk daily from→to, count cards created<=day and done<=day.
	cfd, err := computeCumulativeFlow(db, from, to, assignee, requester)
	if err != nil {
		return nil, err
	}
	m.CumulativeFlow = cfd

	return m, nil
}

func computeCumulativeFlow(db *DB, from, to time.Time, assignee, requester string) ([]CumulativePoint, error) {
	creates := map[string]int{}
	dones := map[string]int{}

	cardWhere := "WHERE deleted_at IS NULL"
	args := []any{}
	if assignee != "" {
		cardWhere += " AND assignee_id=?"
		args = append(args, assignee)
	}
	if requester != "" {
		cardWhere += " AND requester=?"
		args = append(args, requester)
	}

	rows, err := db.conn.Query(`SELECT substr(created_at,1,10), COUNT(*) FROM cards `+cardWhere+` GROUP BY substr(created_at,1,10)`, args...)
	if err != nil {
		return nil, err
	}
	for rows.Next() {
		var d string
		var n int
		if err := rows.Scan(&d, &n); err != nil {
			rows.Close()
			return nil, err
		}
		creates[d] = n
	}
	rows.Close()

	rows, err = db.conn.Query(`SELECT substr(completed_at,1,10), COUNT(*) FROM cards `+cardWhere+` AND completed_at IS NOT NULL AND completed_at != '' GROUP BY substr(completed_at,1,10)`, args...)
	if err != nil {
		return nil, err
	}
	for rows.Next() {
		var d string
		var n int
		if err := rows.Scan(&d, &n); err != nil {
			rows.Close()
			return nil, err
		}
		dones[d] = n
	}
	rows.Close()

	out := []CumulativePoint{}
	totalAcc := 0
	doneAcc := 0
	day := from
	end := to
	if end.Before(day) {
		return out, nil
	}
	for d := day; !d.After(end); d = d.AddDate(0, 0, 1) {
		ds := d.Format("2006-01-02")
		// Sum all creates with key <= ds, all dones with key <= ds.
		// Optimize: track keys already accounted; here we just do once per loop using map sums.
		_ = ds
	}
	// Simpler: collect and sort all create/done dates, sweep.
	type ev struct {
		date    string
		creates int
		dones   int
	}
	all := map[string]*ev{}
	for d, n := range creates {
		all[d] = &ev{date: d, creates: n}
	}
	for d, n := range dones {
		if e, ok := all[d]; ok {
			e.dones = n
		} else {
			all[d] = &ev{date: d, dones: n}
		}
	}
	dates := make([]string, 0, len(all))
	for d := range all {
		dates = append(dates, d)
	}
	sort.Strings(dates)

	// Accumulate up to `from` first.
	fromS := from.Format("2006-01-02")
	idx := 0
	for idx < len(dates) && dates[idx] < fromS {
		totalAcc += all[dates[idx]].creates
		doneAcc += all[dates[idx]].dones
		idx++
	}
	for d := from; !d.After(to); d = d.AddDate(0, 0, 1) {
		ds := d.Format("2006-01-02")
		for idx < len(dates) && dates[idx] <= ds {
			totalAcc += all[dates[idx]].creates
			doneAcc += all[dates[idx]].dones
			idx++
		}
		out = append(out, CumulativePoint{Date: ds, Total: totalAcc, Done: doneAcc})
	}
	return out, nil
}

func condFromCard(assignee, requester, alias, leadKw string) string {
	pref := alias
	if pref != "" {
		pref += "."
	}
	out := ""
	if assignee != "" {
		out += " " + leadKw + " " + pref + "assignee_id=?"
		leadKw = "AND"
	}
	if requester != "" {
		out += " " + leadKw + " " + pref + "requester=?"
	}
	return out
}

func colArgs(assignee, requester string) []any {
	args := []any{}
	if assignee != "" {
		args = append(args, assignee)
	}
	if requester != "" {
		args = append(args, requester)
	}
	return args
}

func cardJoinFilter(requester string) string {
	if requester != "" {
		return " AND c.requester=?"
	}
	return ""
}

func topAssigneeArgs(fromStr, toStr, requester string) []any {
	args := []any{fromStr, toStr}
	if requester != "" {
		args = append(args, requester)
	}
	return args
}

func topReqArgs(fromStr, toStr, assignee string) []any {
	args := []any{fromStr, toStr}
	if assignee != "" {
		args = append(args, assignee)
	}
	return args
}

func collectDurations(db *DB, query string, args ...any) ([]int64, error) {
	rows, err := db.conn.Query(query, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	out := []int64{}
	for rows.Next() {
		var v float64
		if err := rows.Scan(&v); err != nil {
			return nil, err
		}
		if v < 0 {
			v = 0
		}
		out = append(out, int64(v))
	}
	return out, rows.Err()
}

func dailyBucket(db *DB, dateCol, fromStr, toStr, assignee, requester string, requireNonNull bool) ([]DailyCount, error) {
	cardWhere := "deleted_at IS NULL"
	if requireNonNull {
		cardWhere += " AND " + dateCol + " IS NOT NULL AND " + dateCol + " != ''"
	}
	cardWhere += " AND " + dateCol + ">=? AND " + dateCol + "<=?"
	args := []any{fromStr, toStr}
	if assignee != "" {
		cardWhere += " AND assignee_id=?"
		args = append(args, assignee)
	}
	if requester != "" {
		cardWhere += " AND requester=?"
		args = append(args, requester)
	}
	q := `SELECT substr(` + dateCol + `, 1, 10) as d, COUNT(*) FROM cards WHERE ` + cardWhere + ` GROUP BY d ORDER BY d`
	rows, err := db.conn.Query(q, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	out := []DailyCount{}
	for rows.Next() {
		var dc DailyCount
		if err := rows.Scan(&dc.Date, &dc.Count); err != nil {
			return nil, err
		}
		out = append(out, dc)
	}
	return out, rows.Err()
}
