package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"time"

	"fn-registry/functions/infra"
)

const agentRunnerBase = "http://127.0.0.1:8486"

func (s *Server) registerRoutes(mux *http.ServeMux) {
	mux.HandleFunc("/api/health", s.handleHealth)
	mux.HandleFunc("/api/issues", s.handleIssues)
	mux.HandleFunc("/api/issues/", s.handleIssueByID)
	mux.HandleFunc("/api/flows", s.handleFlows)
	mux.HandleFunc("/api/flows/", s.handleFlowByID)
	mux.HandleFunc("/api/meta", s.handleMeta)
	mux.HandleFunc("/api/sse", s.handleSSE)
	mux.HandleFunc("/api/agent_status", s.handleAgentStatus)
	mux.HandleFunc("/api/agent_launch", s.handleAgentLaunch)
}

func writeJSON(w http.ResponseWriter, status int, v any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(v)
}

func writeErr(w http.ResponseWriter, status int, msg string) {
	writeJSON(w, status, map[string]string{"error": msg})
}

func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
	var ni, nf int
	s.db.QueryRow(`SELECT COUNT(*) FROM issues`).Scan(&ni)
	s.db.QueryRow(`SELECT COUNT(*) FROM flows`).Scan(&nf)
	writeJSON(w, 200, map[string]any{
		"ok":            true,
		"version":       "0.1.0",
		"count_issues":  ni,
		"count_flows":   nf,
		"issues_dir":    s.issuesDir,
		"flows_dir":     s.flowsDir,
		"timestamp":     time.Now().UTC().Format(time.RFC3339),
	})
}

func (s *Server) handleIssues(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		writeErr(w, 405, "method not allowed")
		return
	}
	q := r.URL.Query()
	where := []string{"1=1"}
	args := []any{}
	if v := q.Get("status"); v != "" {
		where = append(where, "status=?")
		args = append(args, v)
	}
	if v := q.Get("priority"); v != "" {
		where = append(where, "priority=?")
		args = append(args, v)
	}
	if v := q.Get("scope"); v != "" {
		where = append(where, "scope=?")
		args = append(args, v)
	}
	if v := q.Get("domain"); v != "" {
		where = append(where, "domain_json LIKE ?")
		args = append(args, "%\""+v+"\"%")
	}
	if v := q.Get("tag"); v != "" {
		where = append(where, "tags_json LIKE ?")
		args = append(args, "%\""+v+"\"%")
	}
	if v := q.Get("completed"); v != "" {
		if v == "true" || v == "1" {
			where = append(where, "completed=1")
		} else {
			where = append(where, "completed=0")
		}
	}
	sql := "SELECT id,title,status,type,scope,priority,domain_json,tags_json,depends_json,blocks_json,related_json,flow_id,file_path,completed,created_at,updated_at FROM issues WHERE " + strings.Join(where, " AND ") + " ORDER BY id ASC"
	rows, err := s.db.Query(sql, args...)
	if err != nil {
		writeErr(w, 500, err.Error())
		return
	}
	defer rows.Close()
	out := []map[string]any{}
	for rows.Next() {
		var (
			id, title, status, typ, scope, priority    string
			domJ, tagJ, depJ, blkJ, relJ, flow, path   string
			completedInt                               int
			createdAt, updatedAt                       string
		)
		if err := rows.Scan(&id, &title, &status, &typ, &scope, &priority, &domJ, &tagJ, &depJ, &blkJ, &relJ, &flow, &path, &completedInt, &createdAt, &updatedAt); err != nil {
			writeErr(w, 500, err.Error())
			return
		}
		out = append(out, map[string]any{
			"id":         id,
			"title":      title,
			"status":     status,
			"type":       typ,
			"scope":      scope,
			"priority":   priority,
			"domain":     parseJSONArr(domJ),
			"tags":       parseJSONArr(tagJ),
			"depends":    parseJSONArr(depJ),
			"blocks":     parseJSONArr(blkJ),
			"related":    parseJSONArr(relJ),
			"flow":       flow,
			"file_path":  path,
			"completed":  completedInt == 1,
			"created":    createdAt,
			"updated":    updatedAt,
		})
	}
	writeJSON(w, 200, out)
}

func (s *Server) handleIssueByID(w http.ResponseWriter, r *http.Request) {
	id := strings.TrimPrefix(r.URL.Path, "/api/issues/")
	if id == "" {
		writeErr(w, 400, "missing id")
		return
	}
	switch r.Method {
	case "GET":
		s.getIssue(w, id)
	case "PATCH":
		s.patchIssue(w, r, id)
	default:
		writeErr(w, 405, "method not allowed")
	}
}

func (s *Server) getIssue(w http.ResponseWriter, id string) {
	row := s.db.QueryRow(`SELECT id,title,status,type,scope,priority,domain_json,tags_json,depends_json,blocks_json,related_json,flow_id,body,file_path,completed,created_at,updated_at FROM issues WHERE id=?`, id)
	var (
		iid, title, status, typ, scope, priority string
		domJ, tagJ, depJ, blkJ, relJ, flow, body, path string
		completedInt int
		createdAt, updatedAt string
	)
	if err := row.Scan(&iid, &title, &status, &typ, &scope, &priority, &domJ, &tagJ, &depJ, &blkJ, &relJ, &flow, &body, &path, &completedInt, &createdAt, &updatedAt); err != nil {
		writeErr(w, 404, "not found")
		return
	}
	writeJSON(w, 200, map[string]any{
		"id": iid, "title": title, "status": status, "type": typ, "scope": scope, "priority": priority,
		"domain": parseJSONArr(domJ), "tags": parseJSONArr(tagJ),
		"depends": parseJSONArr(depJ), "blocks": parseJSONArr(blkJ), "related": parseJSONArr(relJ),
		"flow": flow, "body": body, "file_path": path, "completed": completedInt == 1,
		"created": createdAt, "updated": updatedAt,
	})
}

func (s *Server) patchIssue(w http.ResponseWriter, r *http.Request, id string) {
	var patch map[string]any
	if err := json.NewDecoder(r.Body).Decode(&patch); err != nil {
		writeErr(w, 400, "bad json")
		return
	}
	var filePath string
	if err := s.db.QueryRow(`SELECT file_path FROM issues WHERE id=?`, id).Scan(&filePath); err != nil {
		writeErr(w, 404, "not found")
		return
	}
	iss, body, err := infra.ParseIssueMd(filePath)
	if err != nil {
		writeErr(w, 500, fmt.Sprintf("parse: %v", err))
		return
	}
	applyPatch(&iss, patch)
	iss.Updated = time.Now().UTC().Format("2006-01-02")
	if err := infra.WriteIssueMd(filePath, iss, body); err != nil {
		writeErr(w, 500, fmt.Sprintf("write: %v", err))
		return
	}
	info, _ := os.Stat(filePath)
	if info != nil {
		iss.MtimeNs = info.ModTime().UnixNano()
	}
	iss.FilePath = filePath
	iss.Completed = strings.Contains(filePath, "/completed/")
	if err := s.upsertIssueRow(iss); err != nil {
		writeErr(w, 500, err.Error())
		return
	}
	s.hub.broadcast(SSEEvent{Type: "updated", ID: id, Path: filePath})
	s.getIssue(w, id)
}

func applyPatch(iss *infra.Issue, patch map[string]any) {
	if v, ok := patch["status"].(string); ok {
		iss.Status = v
	}
	if v, ok := patch["priority"].(string); ok {
		iss.Priority = v
	}
	if v, ok := patch["scope"].(string); ok {
		iss.Scope = v
	}
	if v, ok := patch["title"].(string); ok {
		iss.Title = v
	}
	if v, ok := patch["type"].(string); ok {
		iss.Type = v
	}
	if v, ok := patch["flow"].(string); ok {
		iss.Flow = v
	}
	for _, k := range []string{"domain", "tags", "depends", "blocks", "related"} {
		if raw, ok := patch[k]; ok {
			arr := []string{}
			if xs, ok := raw.([]any); ok {
				for _, x := range xs {
					if s, ok := x.(string); ok {
						arr = append(arr, s)
					}
				}
			}
			switch k {
			case "domain":
				iss.Domain = arr
			case "tags":
				iss.Tags = arr
			case "depends":
				iss.Depends = arr
			case "blocks":
				iss.Blocks = arr
			case "related":
				iss.Related = arr
			}
		}
	}
}

func (s *Server) handleFlows(w http.ResponseWriter, r *http.Request) {
	rows, err := s.db.Query(`SELECT id,title,status,kind,tags_json,file_path FROM flows ORDER BY id ASC`)
	if err != nil {
		writeErr(w, 500, err.Error())
		return
	}
	defer rows.Close()
	out := []map[string]any{}
	for rows.Next() {
		var id, title, status, kind, tagJ, path string
		rows.Scan(&id, &title, &status, &kind, &tagJ, &path)
		out = append(out, map[string]any{
			"id": id, "title": title, "status": status, "kind": kind,
			"tags": parseJSONArr(tagJ), "file_path": path,
		})
	}
	writeJSON(w, 200, out)
}

func (s *Server) handleFlowByID(w http.ResponseWriter, r *http.Request) {
	id := strings.TrimPrefix(r.URL.Path, "/api/flows/")
	if id == "" {
		writeErr(w, 400, "missing id")
		return
	}
	row := s.db.QueryRow(`SELECT id,title,status,kind,tags_json,body,file_path FROM flows WHERE id=?`, id)
	var iid, title, status, kind, tagJ, body, path string
	if err := row.Scan(&iid, &title, &status, &kind, &tagJ, &body, &path); err != nil {
		writeErr(w, 404, "not found")
		return
	}
	writeJSON(w, 200, map[string]any{
		"id": iid, "title": title, "status": status, "kind": kind,
		"tags": parseJSONArr(tagJ), "body": body, "file_path": path,
	})
}

func (s *Server) handleMeta(w http.ResponseWriter, r *http.Request) {
	writeJSON(w, 200, map[string]any{
		"statuses":         []string{"ideas", "pendiente", "in-progress", "bloqueado", "completado", "deferred", "descartado"},
		"board_columns":    []string{"ideas", "pendiente", "in-progress", "completado"},
		"priorities":       []string{"critica", "alta", "media", "baja"},
		"scopes":           []string{"registry-only", "app-scoped", "multi-app", "cross-stack"},
		"types":            []string{"feature", "bugfix", "refactor", "docs", "chore", "research", "infra", "app", "spike", "epic", "planning"},
	})
}

// GET /api/agent_status — proxies agent_runner_api running runs, returns map issue_id -> run_id
func (s *Server) handleAgentStatus(w http.ResponseWriter, r *http.Request) {
	resp, err := http.Get(agentRunnerBase + "/api/runs?status=running")
	if err != nil {
		writeJSON(w, 200, map[string]any{"available": false, "active": map[string]string{}})
		return
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(resp.Body)
	var runs []map[string]any
	if err := json.Unmarshal(body, &runs); err != nil {
		writeJSON(w, 200, map[string]any{"available": false, "active": map[string]string{}})
		return
	}
	active := map[string]string{}
	for _, run := range runs {
		issueID, _ := run["issue_id"].(string)
		runID, _ := run["id"].(string)
		if issueID != "" && runID != "" {
			active[issueID] = runID
		}
	}
	writeJSON(w, 200, map[string]any{"available": true, "active": active})
}

// POST /api/agent_launch {"issue_id":"NNNN"} — forwards to agent_runner_api
func (s *Server) handleAgentLaunch(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		writeErr(w, 405, "method not allowed")
		return
	}
	var req struct {
		IssueID string `json:"issue_id"`
		Mode    string `json:"mode"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeErr(w, 400, "bad json")
		return
	}
	if req.IssueID == "" {
		writeErr(w, 400, "issue_id required")
		return
	}
	if req.Mode == "" {
		req.Mode = "fix-issue"
	}
	payload, _ := json.Marshal(map[string]string{
		"issue_id":   req.IssueID,
		"mode":       req.Mode,
		"kanban_app": "kanban_cpp",
	})
	resp, err := http.Post(agentRunnerBase+"/api/runs", "application/json", bytes.NewReader(payload))
	if err != nil {
		writeErr(w, 502, fmt.Sprintf("agent_runner_api unreachable: %v", err))
		return
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(resp.Body)
	if resp.StatusCode >= 400 {
		writeErr(w, resp.StatusCode, string(body))
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(resp.StatusCode)
	w.Write(body)
}

func (s *Server) handleSSE(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")
	flusher, ok := w.(http.Flusher)
	if !ok {
		writeErr(w, 500, "streaming unsupported")
		return
	}
	ch := s.hub.subscribe()
	defer s.hub.unsubscribe(ch)

	pingTick := time.NewTicker(15 * time.Second)
	defer pingTick.Stop()

	for {
		select {
		case <-r.Context().Done():
			return
		case ev := <-ch:
			b, _ := json.Marshal(ev)
			fmt.Fprintf(w, "data: %s\n\n", b)
			flusher.Flush()
		case <-pingTick.C:
			fmt.Fprintf(w, ": ping\n\n")
			flusher.Flush()
		}
	}
}

func parseJSONArr(s string) []string {
	if s == "" {
		return []string{}
	}
	var arr []string
	if err := json.Unmarshal([]byte(s), &arr); err != nil {
		return []string{}
	}
	return arr
}

func withMiddleware(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET,POST,PATCH,OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type,Authorization")
		if r.Method == "OPTIONS" {
			w.WriteHeader(204)
			return
		}
		start := time.Now()
		h.ServeHTTP(w, r)
		fmt.Printf("[%s] %s %s %s\n", time.Now().Format("15:04:05"), r.Method, r.URL.Path, time.Since(start))
	})
}
