package handlers

// workflow_assignments.go — Assignment queue for human review stages.
//
// v0.29.0: Raw SQL replaced with WorkflowStore + ChannelStore methods.

import (
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"

	"chat-switchboard/events"
	"chat-switchboard/notifications"
	"chat-switchboard/store"
)

// ── Workflow Assignment Handler ─────────────

type WorkflowAssignmentHandler struct {
	stores store.Stores
	hub    *events.Hub
}

func NewWorkflowAssignmentHandler(stores store.Stores, hub ...*events.Hub) *WorkflowAssignmentHandler {
	h := &WorkflowAssignmentHandler{stores: stores}
	if len(hub) > 0 {
		h.hub = hub[0]
	}
	return h
}

// ListForTeam returns unassigned + claimed assignments for a team.
// GET /api/v1/teams/:teamId/assignments
func (h *WorkflowAssignmentHandler) ListForTeam(c *gin.Context) {
	teamID := c.Param("teamId")
	status := c.DefaultQuery("status", "unassigned")

	result, err := h.stores.Workflows.ListAssignmentsForTeam(c.Request.Context(), teamID, status)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list assignments"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"data": result})
}

// ListMine returns assignments claimed by the current user plus
// unassigned assignments for teams the user belongs to.
// GET /api/v1/workflow-assignments/mine
func (h *WorkflowAssignmentHandler) ListMine(c *gin.Context) {
	userID := c.GetString("user_id")

	result, err := h.stores.Workflows.ListAssignmentsMine(c.Request.Context(), userID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list assignments"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"data": result})
}

// Claim assigns a workflow to the current user via optimistic lock.
// POST /api/v1/workflow-assignments/:id/claim
func (h *WorkflowAssignmentHandler) Claim(c *gin.Context) {
	assignmentID := c.Param("id")
	userID := c.GetString("user_id")
	now := time.Now().UTC()

	n, err := h.stores.Workflows.ClaimAssignment(c.Request.Context(), assignmentID, userID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to claim assignment"})
		return
	}
	if n == 0 {
		c.JSON(http.StatusConflict, gin.H{"error": "assignment already claimed or not found"})
		return
	}

	// Look up channel for WS delivery + notification
	channelID, _ := h.stores.Workflows.GetAssignmentChannelID(c.Request.Context(), assignmentID)

	// Emit workflow.claimed WS event to all user participants in the channel
	if h.hub != nil && channelID != "" {
		payload, _ := json.Marshal(map[string]any{
			"assignment_id": assignmentID,
			"claimed_by":    userID,
			"channel_id":    channelID,
		})
		evt := events.Event{
			Label:   "workflow.claimed",
			Payload: payload,
			Ts:      now.UnixMilli(),
		}
		pids, _ := h.stores.Channels.ListUserParticipantIDs(c.Request.Context(), channelID, "")
		for _, uid := range pids {
			h.hub.PublishToUser(uid, evt)
		}
	}

	// Persist notification for bell/inbox
	if svc := notifications.Default(); svc != nil && channelID != "" {
		notifications.NotifyWorkflowClaimed(svc, userID, assignmentID, channelID)
	}

	c.JSON(http.StatusOK, gin.H{"claimed": true, "assignment_id": assignmentID})
}

// Complete marks an assignment as completed.
// POST /api/v1/workflow-assignments/:id/complete
func (h *WorkflowAssignmentHandler) Complete(c *gin.Context) {
	assignmentID := c.Param("id")

	n, err := h.stores.Workflows.CompleteAssignment(c.Request.Context(), assignmentID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to complete assignment"})
		return
	}
	if n == 0 {
		c.JSON(http.StatusConflict, gin.H{"error": "assignment not in claimed state"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"completed": true, "assignment_id": assignmentID})
}

// ── Lifecycle operations (v0.37.15) ──

// Unclaim returns a claimed assignment to unassigned.
// POST /api/v1/workflow-assignments/:id/unclaim
func (h *WorkflowAssignmentHandler) Unclaim(c *gin.Context) {
	assignmentID := c.Param("id")
	userID := c.GetString("user_id")
	role, _ := c.Get("role")

	// Auth: claimer or team admin or global admin
	a, err := h.stores.Workflows.GetAssignmentByID(c.Request.Context(), assignmentID)
	if err != nil || a == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "assignment not found"})
		return
	}

	if role != "admin" && (a.AssignedTo == nil || *a.AssignedTo != userID) {
		isTA := false
		if h.stores.Teams != nil {
			isTA, _ = h.stores.Teams.IsTeamAdmin(c.Request.Context(), a.TeamID, userID)
		}
		if !isTA {
			c.JSON(http.StatusForbidden, gin.H{"error": "only the claimer or a team admin can unclaim"})
			return
		}
	}

	n, err := h.stores.Workflows.UnclaimAssignment(c.Request.Context(), assignmentID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to unclaim assignment"})
		return
	}
	if n == 0 {
		c.JSON(http.StatusConflict, gin.H{"error": "assignment not in claimed state"})
		return
	}

	// Emit WS event
	channelID, _ := h.stores.Workflows.GetAssignmentChannelID(c.Request.Context(), assignmentID)
	if h.hub != nil && channelID != "" {
		payload, _ := json.Marshal(map[string]any{
			"assignment_id": assignmentID,
			"unclaimed_by":  userID,
			"channel_id":    channelID,
		})
		evt := events.Event{
			Label:   "workflow.unclaimed",
			Payload: payload,
			Ts:      time.Now().UnixMilli(),
		}
		pids, _ := h.stores.Channels.ListUserParticipantIDs(c.Request.Context(), channelID, "")
		for _, uid := range pids {
			h.hub.PublishToUser(uid, evt)
		}
	}

	c.JSON(http.StatusOK, gin.H{"unclaimed": true, "assignment_id": assignmentID})
}

// Reassign changes the assigned_to on a claimed assignment.
// POST /api/v1/workflow-assignments/:id/reassign
func (h *WorkflowAssignmentHandler) Reassign(c *gin.Context) {
	assignmentID := c.Param("id")
	userID := c.GetString("user_id")
	role, _ := c.Get("role")

	var body struct {
		UserID string `json:"user_id"`
	}
	if err := c.ShouldBindJSON(&body); err != nil || body.UserID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "user_id is required"})
		return
	}

	// Auth: team admin or global admin
	a, err := h.stores.Workflows.GetAssignmentByID(c.Request.Context(), assignmentID)
	if err != nil || a == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "assignment not found"})
		return
	}

	if role != "admin" {
		isTA := false
		if h.stores.Teams != nil {
			isTA, _ = h.stores.Teams.IsTeamAdmin(c.Request.Context(), a.TeamID, userID)
		}
		if !isTA {
			c.JSON(http.StatusForbidden, gin.H{"error": "team admin access required"})
			return
		}
	}

	n, err := h.stores.Workflows.ReassignAssignment(c.Request.Context(), assignmentID, body.UserID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to reassign assignment"})
		return
	}
	if n == 0 {
		c.JSON(http.StatusConflict, gin.H{"error": "assignment not in claimed state"})
		return
	}

	// Emit WS event
	channelID, _ := h.stores.Workflows.GetAssignmentChannelID(c.Request.Context(), assignmentID)
	if h.hub != nil && channelID != "" {
		payload, _ := json.Marshal(map[string]any{
			"assignment_id": assignmentID,
			"reassigned_by": userID,
			"new_assignee":  body.UserID,
			"channel_id":    channelID,
		})
		evt := events.Event{
			Label:   "workflow.reassigned",
			Payload: payload,
			Ts:      time.Now().UnixMilli(),
		}
		pids, _ := h.stores.Channels.ListUserParticipantIDs(c.Request.Context(), channelID, "")
		for _, uid := range pids {
			h.hub.PublishToUser(uid, evt)
		}
	}

	c.JSON(http.StatusOK, gin.H{"reassigned": true, "assignment_id": assignmentID, "new_assignee": body.UserID})
}

// CancelAssignment cancels a single assignment.
// POST /api/v1/workflow-assignments/:id/cancel
func (h *WorkflowAssignmentHandler) CancelAssignment(c *gin.Context) {
	assignmentID := c.Param("id")
	userID := c.GetString("user_id")
	role, _ := c.Get("role")

	// Auth: team admin or global admin
	a, err := h.stores.Workflows.GetAssignmentByID(c.Request.Context(), assignmentID)
	if err != nil || a == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "assignment not found"})
		return
	}

	if role != "admin" {
		isTA := false
		if h.stores.Teams != nil {
			isTA, _ = h.stores.Teams.IsTeamAdmin(c.Request.Context(), a.TeamID, userID)
		}
		if !isTA {
			c.JSON(http.StatusForbidden, gin.H{"error": "team admin access required"})
			return
		}
	}

	n, err := h.stores.Workflows.CancelAssignment(c.Request.Context(), assignmentID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to cancel assignment"})
		return
	}
	if n == 0 {
		c.JSON(http.StatusConflict, gin.H{"error": "assignment not in cancellable state"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"cancelled": true, "assignment_id": assignmentID})
}

// CommentOnAssignment adds a review comment to an assignment.
// POST /api/v1/workflow-assignments/:id/comment
func (h *WorkflowAssignmentHandler) CommentOnAssignment(c *gin.Context) {
	assignmentID := c.Param("id")
	userID := c.GetString("user_id")

	var body struct {
		Text string `json:"text"`
	}
	if err := c.ShouldBindJSON(&body); err != nil || body.Text == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "text is required"})
		return
	}

	comment := store.ReviewComment{
		Text:      body.Text,
		UserID:    userID,
		CreatedAt: time.Now().UTC().Format(time.RFC3339),
	}

	if err := h.stores.Workflows.AddReviewComment(c.Request.Context(), assignmentID, comment); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to add comment: " + err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{"ok": true, "comment": comment})
}

// GetAssignment returns a single assignment with review comments.
// GET /api/v1/workflow-assignments/:id
func (h *WorkflowAssignmentHandler) GetAssignment(c *gin.Context) {
	assignmentID := c.Param("id")

	a, err := h.stores.Workflows.GetAssignmentByID(c.Request.Context(), assignmentID)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("assignment not found: %v", err)})
		return
	}

	// Parse review_comments for the response
	var comments []store.ReviewComment
	if len(a.ReviewComments) > 0 {
		_ = json.Unmarshal(a.ReviewComments, &comments)
	}

	c.JSON(http.StatusOK, gin.H{
		"id":              a.ID,
		"channel_id":      a.ChannelID,
		"stage":           a.Stage,
		"team_id":         a.TeamID,
		"assigned_to":     a.AssignedTo,
		"status":          a.Status,
		"review_comments": comments,
		"created_at":      a.CreatedAt,
		"claimed_at":      a.ClaimedAt,
		"completed_at":    a.CompletedAt,
	})
}
