package handlers

import (
	"database/sql"
	"strconv"

	"director/api/pubsub"

	"github.com/gin-gonic/gin"
)

type Handler struct {
	db *sql.DB
	ps *pubsub.PubSub
}

func New(db *sql.DB, ps *pubsub.PubSub) *Handler {
	return &Handler{db: db, ps: ps}
}

func (h *Handler) broadcast(c *gin.Context, event string, data any) {
	h.ps.Publish(c.Request.Context(), event, data)
}

// --- Plans ---

func (h *Handler) ListPlans(c *gin.Context) {
	rows, err := h.db.Query(`
		SELECT id, title, description, team, priority, status,
		       created_at, updated_at, approved_at, started_at, completed_at
		FROM plans ORDER BY
			CASE priority
				WHEN 'critical' THEN 0
				WHEN '1' THEN 1 WHEN '2' THEN 2 WHEN '3' THEN 3
				WHEN '4' THEN 4 WHEN '5' THEN 5 WHEN 'backlog' THEN 6
			END,
			created_at DESC
	`)
	if err != nil {
		c.JSON(500, gin.H{"error": err.Error()})
		return
	}
	defer rows.Close()

	plans := []map[string]any{}
	for rows.Next() {
		var id int
		var title, desc, team, priority, status string
		var createdAt, updatedAt string
		var approvedAt, startedAt, completedAt sql.NullString
		rows.Scan(&id, &title, &desc, &team, &priority, &status, &createdAt, &updatedAt, &approvedAt, &startedAt, &completedAt)
		p := map[string]any{
			"id": id, "title": title, "description": desc, "team": team,
			"priority": priority, "status": status,
			"created_at": createdAt, "updated_at": updatedAt,
		}
		if approvedAt.Valid {
			p["approved_at"] = approvedAt.String
		}
		if startedAt.Valid {
			p["started_at"] = startedAt.String
		}
		if completedAt.Valid {
			p["completed_at"] = completedAt.String
		}
		plans = append(plans, p)
	}
	c.JSON(200, plans)
}

func (h *Handler) CreatePlan(c *gin.Context) {
	var input struct {
		Title       string `json:"title" binding:"required"`
		Description string `json:"description" binding:"required"`
		Team        string `json:"team" binding:"required"`
		Priority    string `json:"priority"`
	}
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(400, gin.H{"error": err.Error()})
		return
	}
	if input.Priority == "" {
		input.Priority = "3"
	}

	var id int
	err := h.db.QueryRow(`
		INSERT INTO plans (title, description, team, priority)
		VALUES ($1, $2, $3, $4) RETURNING id
	`, input.Title, input.Description, input.Team, input.Priority).Scan(&id)
	if err != nil {
		c.JSON(500, gin.H{"error": err.Error()})
		return
	}

	plan := map[string]any{
		"id": id, "title": input.Title, "description": input.Description,
		"team": input.Team, "priority": input.Priority, "status": "draft",
	}
	h.broadcast(c, "plan:created", plan)
	c.JSON(201, plan)
}

func (h *Handler) GetPlan(c *gin.Context) {
	id, _ := strconv.Atoi(c.Param("id"))
	var title, desc, team, priority, status, createdAt, updatedAt string
	var approvedAt, startedAt, completedAt sql.NullString
	err := h.db.QueryRow(`
		SELECT title, description, team, priority, status,
		       created_at, updated_at, approved_at, started_at, completed_at
		FROM plans WHERE id = $1
	`, id).Scan(&title, &desc, &team, &priority, &status, &createdAt, &updatedAt, &approvedAt, &startedAt, &completedAt)
	if err == sql.ErrNoRows {
		c.JSON(404, gin.H{"error": "plan not found"})
		return
	}
	if err != nil {
		c.JSON(500, gin.H{"error": err.Error()})
		return
	}
	out := map[string]any{
		"id": id, "title": title, "description": desc, "team": team,
		"priority": priority, "status": status,
		"created_at": createdAt, "updated_at": updatedAt,
	}
	if approvedAt.Valid {
		out["approved_at"] = approvedAt.String
	}
	if startedAt.Valid {
		out["started_at"] = startedAt.String
	}
	if completedAt.Valid {
		out["completed_at"] = completedAt.String
	}
	c.JSON(200, out)
}

func (h *Handler) UpdatePlan(c *gin.Context) {
	id, _ := strconv.Atoi(c.Param("id"))
	var input map[string]any
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(400, gin.H{"error": err.Error()})
		return
	}

	// Build dynamic update
	allowed := map[string]bool{"title": true, "description": true, "priority": true, "status": true}
	sets := ""
	args := []any{}
	i := 1
	for k, v := range input {
		if !allowed[k] {
			continue
		}
		if sets != "" {
			sets += ", "
		}
		sets += k + " = $" + strconv.Itoa(i)
		args = append(args, v)
		i++
	}
	if sets == "" {
		c.JSON(400, gin.H{"error": "no valid fields"})
		return
	}
	args = append(args, id)
	_, err := h.db.Exec("UPDATE plans SET "+sets+" WHERE id = $"+strconv.Itoa(i), args...)
	if err != nil {
		c.JSON(500, gin.H{"error": err.Error()})
		return
	}
	input["id"] = id
	h.broadcast(c, "plan:updated", input)
	c.JSON(200, input)
}

// --- Tasks ---

func (h *Handler) ListTasks(c *gin.Context) {
	planID, _ := strconv.Atoi(c.Param("id"))
	rows, err := h.db.Query(`
		SELECT id, title, description, task_type, status, assigned_agent, progress_note, sort_order,
		       created_at, updated_at, started_at, completed_at
		FROM tasks WHERE plan_id = $1 ORDER BY sort_order, id
	`, planID)
	if err != nil {
		c.JSON(500, gin.H{"error": err.Error()})
		return
	}
	defer rows.Close()

	tasks := []map[string]any{}
	for rows.Next() {
		var id, sortOrder int
		var title, taskType, status, createdAt, updatedAt string
		var desc, agent, note *string
		var startedAt, completedAt sql.NullString
		rows.Scan(&id, &title, &desc, &taskType, &status, &agent, &note, &sortOrder,
			&createdAt, &updatedAt, &startedAt, &completedAt)
		t := map[string]any{
			"id": id, "plan_id": planID, "title": title, "task_type": taskType,
			"status": status, "sort_order": sortOrder,
			"created_at": createdAt, "updated_at": updatedAt,
		}
		if desc != nil {
			t["description"] = *desc
		}
		if agent != nil {
			t["assigned_agent"] = *agent
		}
		if note != nil {
			t["progress_note"] = *note
		}
		if startedAt.Valid {
			t["started_at"] = startedAt.String
		}
		if completedAt.Valid {
			t["completed_at"] = completedAt.String
		}
		tasks = append(tasks, t)
	}
	c.JSON(200, tasks)
}

func (h *Handler) CreateTask(c *gin.Context) {
	planID, _ := strconv.Atoi(c.Param("id"))
	var input struct {
		Title         string `json:"title" binding:"required"`
		Description   string `json:"description"`
		TaskType      string `json:"task_type"`
		AssignedAgent string `json:"assigned_agent"`
		SortOrder     int    `json:"sort_order"`
	}
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(400, gin.H{"error": err.Error()})
		return
	}
	if input.TaskType == "" {
		input.TaskType = "core"
	}

	var id int
	err := h.db.QueryRow(`
		INSERT INTO tasks (plan_id, title, description, task_type, assigned_agent, sort_order)
		VALUES ($1, $2, $3, $4, $5, $6) RETURNING id
	`, planID, input.Title, input.Description, input.TaskType, input.AssignedAgent, input.SortOrder).Scan(&id)
	if err != nil {
		c.JSON(500, gin.H{"error": err.Error()})
		return
	}

	task := map[string]any{
		"id": id, "plan_id": planID, "title": input.Title,
		"task_type": input.TaskType, "status": "queued",
	}
	h.broadcast(c, "task:created", task)
	c.JSON(201, task)
}

func (h *Handler) UpdateTask(c *gin.Context) {
	id, _ := strconv.Atoi(c.Param("id"))
	var input map[string]any
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(400, gin.H{"error": err.Error()})
		return
	}

	allowed := map[string]bool{"status": true, "assigned_agent": true, "progress_note": true, "blockers": true}
	sets := ""
	args := []any{}
	i := 1
	for k, v := range input {
		if !allowed[k] {
			continue
		}
		if sets != "" {
			sets += ", "
		}
		sets += k + " = $" + strconv.Itoa(i)
		args = append(args, v)
		i++
	}
	if sets == "" {
		c.JSON(400, gin.H{"error": "no valid fields"})
		return
	}
	args = append(args, id)
	_, err := h.db.Exec("UPDATE tasks SET "+sets+" WHERE id = $"+strconv.Itoa(i), args...)
	if err != nil {
		c.JSON(500, gin.H{"error": err.Error()})
		return
	}
	input["id"] = id
	h.broadcast(c, "task:updated", input)
	c.JSON(200, input)
}

// --- Agents ---

func (h *Handler) ListAgents(c *gin.Context) {
	rows, err := h.db.Query(`
		SELECT id, name, team, role, model_provider, model_id, gpu_default, status, current_task_id, last_seen_at, specialization
		FROM agents ORDER BY team, role
	`)
	if err != nil {
		c.JSON(500, gin.H{"error": err.Error()})
		return
	}
	defer rows.Close()

	agents := []map[string]any{}
	for rows.Next() {
		var id, name, team, role, provider, model, status string
		var gpu, lastSeen, specialization *string
		var taskID *int
		rows.Scan(&id, &name, &team, &role, &provider, &model, &gpu, &status, &taskID, &lastSeen, &specialization)
		a := map[string]any{
			"id": id, "name": name, "team": team, "role": role,
			"model_provider": provider, "model_id": model, "status": status,
		}
		if gpu != nil {
			a["gpu_default"] = *gpu
		}
		if taskID != nil {
			a["current_task_id"] = *taskID
		}
		if lastSeen != nil {
			a["last_seen_at"] = *lastSeen
		}
		if specialization != nil {
			a["specialization"] = *specialization
		}
		agents = append(agents, a)
	}
	c.JSON(200, agents)
}

func (h *Handler) UpdateAgent(c *gin.Context) {
	id := c.Param("id")
	var input map[string]any
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(400, gin.H{"error": err.Error()})
		return
	}

	allowed := map[string]bool{"status": true, "current_task_id": true}
	sets := ""
	args := []any{}
	i := 1
	for k, v := range input {
		if !allowed[k] {
			continue
		}
		if sets != "" {
			sets += ", "
		}
		sets += k + " = $" + strconv.Itoa(i)
		args = append(args, v)
		i++
	}
	if sets == "" {
		c.JSON(400, gin.H{"error": "no valid fields"})
		return
	}
	args = append(args, id)
	_, err := h.db.Exec("UPDATE agents SET "+sets+", last_seen_at = now() WHERE id = $"+strconv.Itoa(i), args...)
	if err != nil {
		c.JSON(500, gin.H{"error": err.Error()})
		return
	}
	input["id"] = id
	h.broadcast(c, "agent:updated", input)
	c.JSON(200, input)
}

// --- Director Queue ---

func (h *Handler) ListQueue(c *gin.Context) {
	status := c.DefaultQuery("status", "ready_for_approval")
	rows, err := h.db.Query(`
		SELECT dq.id, dq.plan_id, dq.task_id, dq.status, dq.verification_notes,
			   dq.director_notes, dq.submitted_at, dq.reviewed_at,
			   COALESCE(p.title, ''), COALESCE(t.title, '')
		FROM director_queue dq
		LEFT JOIN plans p ON dq.plan_id = p.id
		LEFT JOIN tasks t ON dq.task_id = t.id
		WHERE dq.status = $1
		ORDER BY dq.submitted_at
	`, status)
	if err != nil {
		c.JSON(500, gin.H{"error": err.Error()})
		return
	}
	defer rows.Close()

	items := []map[string]any{}
	for rows.Next() {
		var id int
		var planID, taskID *int
		var qStatus, planTitle, taskTitle string
		var verNotes, dirNotes *string
		var submittedAt string
		var reviewedAt *string
		rows.Scan(&id, &planID, &taskID, &qStatus, &verNotes, &dirNotes, &submittedAt, &reviewedAt, &planTitle, &taskTitle)
		item := map[string]any{
			"id": id, "status": qStatus, "submitted_at": submittedAt,
			"plan_title": planTitle, "task_title": taskTitle,
		}
		if planID != nil {
			item["plan_id"] = *planID
		}
		if taskID != nil {
			item["task_id"] = *taskID
		}
		if verNotes != nil {
			item["verification_notes"] = *verNotes
		}
		if dirNotes != nil {
			item["director_notes"] = *dirNotes
		}
		if reviewedAt != nil {
			item["reviewed_at"] = *reviewedAt
		}
		items = append(items, item)
	}
	c.JSON(200, items)
}

func (h *Handler) UpdateQueueItem(c *gin.Context) {
	id, _ := strconv.Atoi(c.Param("id"))
	var input struct {
		Status        string `json:"status" binding:"required"`
		DirectorNotes string `json:"director_notes"`
	}
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(400, gin.H{"error": err.Error()})
		return
	}
	_, err := h.db.Exec(`
		UPDATE director_queue SET status = $1, director_notes = $2, reviewed_at = now() WHERE id = $3
	`, input.Status, input.DirectorNotes, id)
	if err != nil {
		c.JSON(500, gin.H{"error": err.Error()})
		return
	}
	result := map[string]any{"id": id, "status": input.Status}
	h.broadcast(c, "queue:updated", result)
	c.JSON(200, result)
}

// --- Pending Questions ---

func (h *Handler) CreateQuestion(c *gin.Context) {
	var input struct {
		PlanID   *int   `json:"plan_id"`
		TaskID   *int   `json:"task_id"`
		FromRole string `json:"from_role" binding:"required"`
		FromID   string `json:"from_id" binding:"required"`
		Question string `json:"question" binding:"required"`
		Context  string `json:"context"`
		Priority string `json:"priority"`
	}
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(400, gin.H{"error": err.Error()})
		return
	}
	if input.Priority == "" {
		input.Priority = "3"
	}

	var id int
	err := h.db.QueryRow(`
		INSERT INTO pending_questions (plan_id, task_id, from_role, from_id, question, context, priority)
		VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id
	`, input.PlanID, input.TaskID, input.FromRole, input.FromID, input.Question, input.Context, input.Priority).Scan(&id)
	if err != nil {
		c.JSON(500, gin.H{"error": err.Error()})
		return
	}

	q := map[string]any{
		"id": id, "from_role": input.FromRole, "from_id": input.FromID,
		"question": input.Question, "priority": input.Priority, "status": "awaiting_director",
	}
	if input.PlanID != nil {
		q["plan_id"] = *input.PlanID
	}
	if input.TaskID != nil {
		q["task_id"] = *input.TaskID
	}
	if input.Context != "" {
		q["context"] = input.Context
	}
	h.broadcast(c, "question:created", q)
	c.JSON(201, q)
}

func (h *Handler) ListQuestions(c *gin.Context) {
	statusFilter := c.DefaultQuery("status", "awaiting_director")
	rows, err := h.db.Query(`
		SELECT pq.id, pq.plan_id, pq.task_id, pq.from_role, pq.from_id, pq.question, pq.context,
		       pq.priority, pq.status, pq.answer, pq.created_at, pq.answered_at,
		       COALESCE(p.title,''), COALESCE(t.title,'')
		FROM pending_questions pq
		LEFT JOIN plans p ON pq.plan_id = p.id
		LEFT JOIN tasks t ON pq.task_id = t.id
		WHERE pq.status = $1
		ORDER BY
			CASE pq.priority
				WHEN 'critical' THEN 0
				WHEN '1' THEN 1 WHEN '2' THEN 2 WHEN '3' THEN 3
				WHEN '4' THEN 4 WHEN '5' THEN 5
			END,
			pq.created_at
	`, statusFilter)
	if err != nil {
		c.JSON(500, gin.H{"error": err.Error()})
		return
	}
	defer rows.Close()

	questions := []map[string]any{}
	for rows.Next() {
		var id int
		var planID, taskID *int
		var fromRole, fromID, question, priority, status, createdAt string
		var ctx, answer, answeredAt *string
		var planTitle, taskTitle string
		rows.Scan(&id, &planID, &taskID, &fromRole, &fromID, &question, &ctx, &priority, &status,
			&answer, &createdAt, &answeredAt, &planTitle, &taskTitle)
		q := map[string]any{
			"id": id, "from_role": fromRole, "from_id": fromID,
			"question": question, "priority": priority, "status": status,
			"created_at": createdAt,
			"plan_title": planTitle, "task_title": taskTitle,
		}
		if planID != nil {
			q["plan_id"] = *planID
		}
		if taskID != nil {
			q["task_id"] = *taskID
		}
		if ctx != nil {
			q["context"] = *ctx
		}
		if answer != nil {
			q["answer"] = *answer
		}
		if answeredAt != nil {
			q["answered_at"] = *answeredAt
		}
		questions = append(questions, q)
	}
	c.JSON(200, questions)
}

func (h *Handler) AnswerQuestion(c *gin.Context) {
	id, _ := strconv.Atoi(c.Param("id"))
	var input struct {
		Answer string `json:"answer" binding:"required"`
	}
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(400, gin.H{"error": err.Error()})
		return
	}
	_, err := h.db.Exec(`
		UPDATE pending_questions SET answer = $1, status = 'answered', answered_at = now() WHERE id = $2
	`, input.Answer, id)
	if err != nil {
		c.JSON(500, gin.H{"error": err.Error()})
		return
	}
	result := map[string]any{"id": id, "status": "answered"}
	h.broadcast(c, "question:answered", result)
	c.JSON(200, result)
}

// --- Messages ---

func (h *Handler) ListMessages(c *gin.Context) {
	planID, _ := strconv.Atoi(c.Param("id"))
	rows, err := h.db.Query(`
		SELECT id, task_id, reply_to_id, from_role, from_id, message, message_type, created_at
		FROM messages WHERE plan_id = $1 ORDER BY created_at
	`, planID)
	if err != nil {
		c.JSON(500, gin.H{"error": err.Error()})
		return
	}
	defer rows.Close()

	messages := []map[string]any{}
	for rows.Next() {
		var id int
		var taskID, replyToID *int
		var fromRole, fromID, message, msgType, createdAt string
		rows.Scan(&id, &taskID, &replyToID, &fromRole, &fromID, &message, &msgType, &createdAt)
		m := map[string]any{
			"id": id, "plan_id": planID, "from_role": fromRole, "from_id": fromID,
			"message": message, "message_type": msgType, "created_at": createdAt,
		}
		if taskID != nil {
			m["task_id"] = *taskID
		}
		if replyToID != nil {
			m["reply_to_id"] = *replyToID
		}
		messages = append(messages, m)
	}
	c.JSON(200, messages)
}

func (h *Handler) CreateMessage(c *gin.Context) {
	planID, _ := strconv.Atoi(c.Param("id"))
	var input struct {
		TaskID      *int   `json:"task_id"`
		ReplyToID   *int   `json:"reply_to_id"`
		FromRole    string `json:"from_role" binding:"required"`
		FromID      string `json:"from_id" binding:"required"`
		Message     string `json:"message" binding:"required"`
		MessageType string `json:"message_type"`
	}
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(400, gin.H{"error": err.Error()})
		return
	}
	if input.MessageType == "" {
		input.MessageType = "comment"
	}

	var id int
	err := h.db.QueryRow(`
		INSERT INTO messages (plan_id, task_id, reply_to_id, from_role, from_id, message, message_type)
		VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id
	`, planID, input.TaskID, input.ReplyToID, input.FromRole, input.FromID, input.Message, input.MessageType).Scan(&id)
	if err != nil {
		c.JSON(500, gin.H{"error": err.Error()})
		return
	}

	msg := map[string]any{
		"id": id, "plan_id": planID, "from_role": input.FromRole,
		"from_id": input.FromID, "message": input.Message,
		"message_type": input.MessageType,
	}
	if input.TaskID != nil {
		msg["task_id"] = *input.TaskID
	}
	if input.ReplyToID != nil {
		msg["reply_to_id"] = *input.ReplyToID
	}
	h.broadcast(c, "message:created", msg)
	c.JSON(201, msg)
}

func (h *Handler) UpdateMessage(c *gin.Context) {
	id, _ := strconv.Atoi(c.Param("id"))
	var input struct {
		Message string `json:"message" binding:"required"`
	}
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(400, gin.H{"error": err.Error()})
		return
	}
	// Only allow editing director-authored messages — never mutate agent or lead history
	var fromRole string
	err := h.db.QueryRow(`SELECT from_role FROM messages WHERE id = $1`, id).Scan(&fromRole)
	if err == sql.ErrNoRows {
		c.JSON(404, gin.H{"error": "message not found"})
		return
	}
	if err != nil {
		c.JSON(500, gin.H{"error": err.Error()})
		return
	}
	if fromRole != "director" {
		c.JSON(403, gin.H{"error": "only director-authored messages can be edited"})
		return
	}
	_, err = h.db.Exec(`UPDATE messages SET message = $1 WHERE id = $2`, input.Message, id)
	if err != nil {
		c.JSON(500, gin.H{"error": err.Error()})
		return
	}
	result := map[string]any{"id": id, "message": input.Message}
	h.broadcast(c, "message:updated", result)
	c.JSON(200, result)
}

func intParam(c *gin.Context, name string) int {
	v, _ := strconv.Atoi(c.Param(name))
	return v
}

func sendError(c *gin.Context, code int, err error) {
	c.JSON(code, gin.H{"error": err.Error()})
}
