package httpapi

import (
	"encoding/json"
	"log/slog"
	"net/http"
	"strconv"
	"strings"

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

// activityItem is one row in the global activity feed — a human-readable
// projection of an execution, independent of the paginated/alphabetically-
// sorted ListExecutions (which orders by target slug for entity-scoped
// browsing, not recency — wrong shape for "what just happened").
type activityItem struct {
	ID          string  `json:"id"`
	Target      string  `json:"target"`
	Verb        string  `json:"verb"`    // e.g. "run", "pct_create", "systemctl"
	Summary     string  `json:"summary"` // human-readable: the command, or purpose, or action detail
	RiskClass   string  `json:"risk_class"`
	Status      string  `json:"status"`
	DurationMs  *int    `json:"duration_ms"`
	Error       string  `json:"error,omitempty"`
	CreatedAt   string  `json:"created_at"`
	CompletedAt *string `json:"completed_at"`
}

// splitAction parses the "verb:params" encoding used throughout executions.action
// (see internal/mcp/server.go) into a verb and a human-readable summary. For
// `run`, params is JSON {command, purpose} — show the purpose if present
// (it's written for a human), falling back to the raw command. For other
// actions (pct_create, systemctl, apt_upgrade, pct_exec), params is either a
// JSON blob or a short flag string — truncate either as a fallback summary.
func splitAction(action string) (verb, summary string) {
	idx := strings.IndexByte(action, ':')
	if idx < 0 {
		return action, ""
	}
	verb, params := action[:idx], action[idx+1:]
	if verb == "run" {
		var p struct {
			Command string `json:"command"`
			Purpose string `json:"purpose"`
		}
		if json.Unmarshal([]byte(params), &p) == nil {
			if p.Purpose != "" {
				return verb, p.Purpose
			}
			return verb, p.Command
		}
	}
	if verb == "pct_create" {
		var p struct {
			Hostname string `json:"hostname"`
		}
		if json.Unmarshal([]byte(params), &p) == nil && p.Hostname != "" {
			return verb, "provision " + p.Hostname
		}
	}
	if len(params) > 140 {
		params = params[:140] + "…"
	}
	return verb, params
}

// serveRecentActivity backs the Operations page's live activity feed — the
// global "what is the system doing / what did it just do" view, recency-
// ordered (unlike ListExecutions, which sorts by target for pagination).
// Custom route, same shape/rationale as serveRecentKnowledge.
func (s *Server) serveRecentActivity(w http.ResponseWriter, req *http.Request) {
	ctx := req.Context()
	limit := 50
	if l := req.URL.Query().Get("limit"); l != "" {
		if n, err := strconv.Atoi(l); err == nil && n > 0 && n <= 200 {
			limit = n
		}
	}

	rows, err := s.pool.Query(ctx, `
		SELECT e.entity_id, te.slug, e.action, e.risk_class, e.status,
		       e.duration_ms, e.result, e.created_at::text, e.completed_at::text
		FROM executions e
		JOIN entities te ON te.id = e.target_entity_id
		ORDER BY e.created_at DESC
		LIMIT $1`, limit)
	if err != nil {
		writeProblem(w, req, http.StatusInternalServerError, "query failed", err.Error())
		return
	}
	defer rows.Close()

	items := []activityItem{}
	for rows.Next() {
		var it activityItem
		var action string
		var resultBytes []byte
		var completedAt *string
		if err := rows.Scan(&it.ID, &it.Target, &action, &it.RiskClass, &it.Status,
			&it.DurationMs, &resultBytes, &it.CreatedAt, &completedAt); err != nil {
			slog.Error("httpapi: activity/recent row scan failed", "error", err)
			continue
		}
		it.Verb, it.Summary = splitAction(action)
		it.CompletedAt = completedAt
		if len(resultBytes) > 0 {
			var result map[string]any
			if json.Unmarshal(resultBytes, &result) == nil {
				if e, ok := result["error"].(string); ok && e != "" {
					if len(e) > 200 {
						e = e[:200] + "…"
					}
					it.Error = e
				}
			}
		}
		items = append(items, it)
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]any{"items": items})
}

// sessionDigestItem summarizes one execution for the session digest.
type sessionDigestItem struct {
	Target    string `json:"target"`
	Verb      string `json:"verb"`
	Summary   string `json:"summary"`
	RiskClass string `json:"risk_class"`
	Status    string `json:"status"`
}

// serveSessionDigest answers "what did THIS chat session actually do" —
// commands run (grouped by outcome), distinct entities touched, and knowledge
// written during the session's time window. Uses nomos_plan_executions (the
// session<->execution link added for auto-continuation) as the source of
// truth for which executions belong to this session; knowledge correlation is
// a best-effort time-window match since knowledge_entities has no session_id.
func (s *Server) serveSessionDigest(w http.ResponseWriter, req *http.Request) {
	ctx := req.Context()
	sessionID := chi.URLParam(req, "id")
	if sessionID == "" {
		writeProblem(w, req, http.StatusBadRequest, "missing session id", "")
		return
	}

	rows, err := s.pool.Query(ctx, `
		SELECT te.slug, e.action, e.risk_class, e.status
		FROM nomos_plan_executions l
		JOIN executions e ON e.entity_id = l.execution_id
		JOIN entities te ON te.id = e.target_entity_id
		WHERE l.session_id = $1
		ORDER BY e.created_at`, sessionID)
	if err != nil {
		writeProblem(w, req, http.StatusInternalServerError, "query failed", err.Error())
		return
	}
	defer rows.Close()

	items := []sessionDigestItem{}
	byStatus := map[string]int{}
	targets := map[string]bool{}
	for rows.Next() {
		var it sessionDigestItem
		var action string
		if err := rows.Scan(&it.Target, &action, &it.RiskClass, &it.Status); err != nil {
			continue
		}
		it.Verb, it.Summary = splitAction(action)
		items = append(items, it)
		byStatus[it.Status]++
		targets[it.Target] = true
	}

	entityList := make([]string, 0, len(targets))
	for t := range targets {
		entityList = append(entityList, t)
	}

	// Best-effort knowledge correlation: notes the agent wrote during this
	// session's active window. Not exact (no session_id on knowledge_entities)
	// but close enough to show "you learned N things in this session".
	var knowledgeTitles []string
	krows, err := s.pool.Query(ctx, `
		SELECT ke.title FROM knowledge_entities ke
		WHERE ke.source = 'nomos-agent'
		  AND ke.updated_at BETWEEN
		    (SELECT COALESCE(MIN(created_at), now()) FROM agent_messages WHERE session_id = $1)
		    AND
		    (SELECT COALESCE(MAX(created_at), now()) + interval '2 minutes' FROM agent_messages WHERE session_id = $1)
		ORDER BY ke.updated_at`, sessionID)
	if err == nil {
		defer krows.Close()
		for krows.Next() {
			var t string
			if krows.Scan(&t) == nil {
				knowledgeTitles = append(knowledgeTitles, t)
			}
		}
	}
	if knowledgeTitles == nil {
		knowledgeTitles = []string{}
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]any{
		"session_id":        sessionID,
		"total_executions":  len(items),
		"by_status":         byStatus,
		"entities_touched":  entityList,
		"executions":        items,
		"knowledge_created": knowledgeTitles,
	})
}
