package handlers

import (
	"database/sql"
	"encoding/json"
	"net/http"
	"regexp"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"

	"switchboard-core/models"
	"switchboard-core/store"
)

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

type WorkflowHandler struct {
	stores store.Stores
}

func NewWorkflowHandler(stores store.Stores) *WorkflowHandler {
	return &WorkflowHandler{stores: stores}
}

var slugRe = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*[a-z0-9]$`)

func validSlug(s string) bool {
	if len(s) < 2 || len(s) > 64 {
		return false
	}
	return slugRe.MatchString(s)
}

// ── Workflow CRUD ───────────────────────────

// List returns workflows visible to the caller.
// GET /api/v1/workflows?team_id=...
func (h *WorkflowHandler) List(c *gin.Context) {
	teamID := c.Query("team_id")
	var result []models.Workflow
	var err error
	if teamID != "" {
		result, err = h.stores.Workflows.ListForTeam(c.Request.Context(), teamID)
	} else {
		result, err = h.stores.Workflows.ListGlobal(c.Request.Context())
	}
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list workflows"})
		return
	}
	if result == nil {
		result = []models.Workflow{}
	}
	c.JSON(http.StatusOK, gin.H{"data": result})
}

// Get returns a single workflow with its stages.
// GET /api/v1/workflows/:id
func (h *WorkflowHandler) Get(c *gin.Context) {
	w, err := h.stores.Workflows.GetByID(c.Request.Context(), c.Param("id"))
	if err != nil {
		if err == sql.ErrNoRows {
			c.JSON(http.StatusNotFound, gin.H{"error": "workflow not found"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get workflow: " + err.Error()})
		return
	}
	stages, _ := h.stores.Workflows.ListStages(c.Request.Context(), w.ID)
	if stages == nil {
		stages = []models.WorkflowStage{}
	}
	w.Stages = stages
	c.JSON(http.StatusOK, w)
}

// Create creates a new workflow definition.
// POST /api/v1/workflows
func (h *WorkflowHandler) Create(c *gin.Context) {
	var w models.Workflow
	if err := c.ShouldBindJSON(&w); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	if w.Name == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "name is required"})
		return
	}
	if w.Slug == "" {
		w.Slug = slugify(w.Name)
	}
	if !validSlug(w.Slug) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "slug must be 2-64 chars, lowercase alphanumeric and hyphens"})
		return
	}
	if w.EntryMode == "" {
		w.EntryMode = "public_link"
	}
	if w.EntryMode != "public_link" && w.EntryMode != "team_only" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "entry_mode must be public_link or team_only"})
		return
	}

	w.CreatedBy = c.GetString("user_id")

	// v0.31.2: team-scoped route injects force_team_id via CreateTeamWorkflow
	if ftid, ok := c.Get("force_team_id"); ok {
		tid := ftid.(string)
		w.TeamID = &tid
	}

	if err := h.stores.Workflows.Create(c.Request.Context(), &w); err != nil {
		if strings.Contains(err.Error(), "unique") || strings.Contains(err.Error(), "UNIQUE") {
			c.JSON(http.StatusConflict, gin.H{"error": "slug already exists in this scope"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create workflow: " + err.Error()})
		return
	}
	c.JSON(http.StatusCreated, w)
}

// Update patches a workflow definition.
// PATCH /api/v1/workflows/:id
func (h *WorkflowHandler) Update(c *gin.Context) {
	id := c.Param("id")
	var patch models.WorkflowPatch
	if err := c.ShouldBindJSON(&patch); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if patch.EntryMode != nil && *patch.EntryMode != "public_link" && *patch.EntryMode != "team_only" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "entry_mode must be public_link or team_only"})
		return
	}
	if err := h.stores.Workflows.Update(c.Request.Context(), id, patch); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update workflow"})
		return
	}
	w, _ := h.stores.Workflows.GetByID(c.Request.Context(), id)
	if w == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "workflow not found"})
		return
	}
	c.JSON(http.StatusOK, w)
}

// Delete deletes a workflow and all its stages/versions (CASCADE).
// DELETE /api/v1/workflows/:id
func (h *WorkflowHandler) Delete(c *gin.Context) {
	if err := h.stores.Workflows.Delete(c.Request.Context(), c.Param("id")); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete workflow"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"deleted": true})
}

// ── Stage CRUD ──────────────────────────────

// ListStages returns stages for a workflow, ordered by ordinal.
// GET /api/v1/workflows/:id/stages
func (h *WorkflowHandler) ListStages(c *gin.Context) {
	stages, err := h.stores.Workflows.ListStages(c.Request.Context(), c.Param("id"))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list stages"})
		return
	}
	if stages == nil {
		stages = []models.WorkflowStage{}
	}
	c.JSON(http.StatusOK, gin.H{"data": stages})
}

// CreateStage adds a stage to a workflow.
// POST /api/v1/workflows/:id/stages
func (h *WorkflowHandler) CreateStage(c *gin.Context) {
	var st models.WorkflowStage
	if err := c.ShouldBindJSON(&st); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	st.WorkflowID = c.Param("id")
	if st.Name == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "name is required"})
		return
	}
	if st.StageMode == "" {
		st.StageMode = models.StageModeDelegated
	}
	if !models.ValidStageModes[st.StageMode] {
		c.JSON(http.StatusBadRequest, gin.H{"error": "stage_mode must be form, review, delegated, or automated"})
		return
	}
	if st.Audience == "" {
		st.Audience = models.AudienceTeam
	}
	if !models.ValidAudiences[st.Audience] {
		c.JSON(http.StatusBadRequest, gin.H{"error": "audience must be team, public, or system"})
		return
	}
	if st.StageType == "" {
		st.StageType = models.StageTypeSimple
	}
	if !models.ValidStageTypes[st.StageType] {
		c.JSON(http.StatusBadRequest, gin.H{"error": "stage_type must be simple, dynamic, or automated"})
		return
	}
	if st.StageMode == models.StageModeDelegated && st.SurfacePkgID == nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "delegated mode requires surface_pkg_id"})
		return
	}
	if (st.StageType == models.StageTypeDynamic || st.StageType == models.StageTypeAutomated) && st.StarlarkHook == nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "dynamic/automated stage_type requires starlark_hook"})
		return
	}
	if st.Ordinal == 0 {
		existing, _ := h.stores.Workflows.ListStages(c.Request.Context(), st.WorkflowID)
		st.Ordinal = len(existing)
	}
	if err := h.stores.Workflows.CreateStage(c.Request.Context(), &st); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create stage: " + err.Error()})
		return
	}
	c.JSON(http.StatusCreated, st)
}

// UpdateStage updates a stage.
// PUT /api/v1/workflows/:id/stages/:sid
func (h *WorkflowHandler) UpdateStage(c *gin.Context) {
	var st models.WorkflowStage
	if err := c.ShouldBindJSON(&st); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	st.ID = c.Param("sid")
	st.WorkflowID = c.Param("id")
	if st.StageMode != "" && !models.ValidStageModes[st.StageMode] {
		c.JSON(http.StatusBadRequest, gin.H{"error": "stage_mode must be form, review, delegated, or automated"})
		return
	}
	if st.Audience != "" && !models.ValidAudiences[st.Audience] {
		c.JSON(http.StatusBadRequest, gin.H{"error": "audience must be team, public, or system"})
		return
	}
	if st.StageType != "" && !models.ValidStageTypes[st.StageType] {
		c.JSON(http.StatusBadRequest, gin.H{"error": "stage_type must be simple, dynamic, or automated"})
		return
	}
	if st.StageMode == models.StageModeDelegated && st.SurfacePkgID == nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "delegated mode requires surface_pkg_id"})
		return
	}
	if (st.StageType == models.StageTypeDynamic || st.StageType == models.StageTypeAutomated) && st.StarlarkHook == nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "dynamic/automated stage_type requires starlark_hook"})
		return
	}
	if err := h.stores.Workflows.UpdateStage(c.Request.Context(), &st); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update stage"})
		return
	}
	c.JSON(http.StatusOK, st)
}

// DeleteStage removes a stage.
// DELETE /api/v1/workflows/:id/stages/:sid
func (h *WorkflowHandler) DeleteStage(c *gin.Context) {
	if err := h.stores.Workflows.DeleteStage(c.Request.Context(), c.Param("sid")); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete stage"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"deleted": true})
}

// ReorderStages sets the ordinal for all stages.
// PATCH /api/v1/workflows/:id/stages/reorder
func (h *WorkflowHandler) ReorderStages(c *gin.Context) {
	var body struct {
		OrderedIDs []string `json:"ordered_ids"`
	}
	if err := c.ShouldBindJSON(&body); err != nil || len(body.OrderedIDs) == 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "ordered_ids required"})
		return
	}
	if err := h.stores.Workflows.ReorderStages(c.Request.Context(), c.Param("id"), body.OrderedIDs); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to reorder stages"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"reordered": true})
}

// ── Publish ─────────────────────────────────

// Publish creates an immutable version snapshot of the current workflow state.
// POST /api/v1/workflows/:id/publish
func (h *WorkflowHandler) Publish(c *gin.Context) {
	wfID := c.Param("id")
	w, err := h.stores.Workflows.GetByID(c.Request.Context(), wfID)
	if err != nil {
		if err == sql.ErrNoRows {
			c.JSON(http.StatusNotFound, gin.H{"error": "workflow not found"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get workflow: " + err.Error()})
		return
	}

	stages, _ := h.stores.Workflows.ListStages(c.Request.Context(), wfID)
	if stages == nil {
		stages = []models.WorkflowStage{}
	}

	// Snapshot stages at publish time (frozen for running instances)
	type stageSnapshot struct {
		models.WorkflowStage
		ToolGrants []string `json:"tool_grants,omitempty"`
	}
	snapshotStages := make([]stageSnapshot, len(stages))
	for i, st := range stages {
		snapshotStages[i] = stageSnapshot{WorkflowStage: st}
	}

	snapshot := map[string]interface{}{
		"workflow": w,
		"stages":   snapshotStages,
	}
	snapshotJSON, err := json.Marshal(snapshot)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to serialize snapshot"})
		return
	}

	v := &models.WorkflowVersion{
		WorkflowID:    wfID,
		VersionNumber: w.Version,
		Snapshot:      snapshotJSON,
	}

	existing, _ := h.stores.Workflows.GetVersion(c.Request.Context(), wfID, w.Version)
	if existing != nil {
		c.JSON(http.StatusConflict, gin.H{"error": "version already published — edit the workflow to increment"})
		return
	}

	if err := h.stores.Workflows.Publish(c.Request.Context(), v); err != nil {
		if strings.Contains(err.Error(), "unique") || strings.Contains(err.Error(), "UNIQUE") {
			c.JSON(http.StatusConflict, gin.H{"error": "version already published"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to publish: " + err.Error()})
		return
	}
	c.JSON(http.StatusCreated, v)
}

// GetVersion returns a specific version snapshot.
// GET /api/v1/workflows/:id/versions/:version
func (h *WorkflowHandler) GetVersion(c *gin.Context) {
	wfID := c.Param("id")
	vNum, err := strconv.Atoi(c.Param("version"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid version number"})
		return
	}
	v, err := h.stores.Workflows.GetVersion(c.Request.Context(), wfID, vNum)
	if err != nil {
		if err == sql.ErrNoRows {
			c.JSON(http.StatusNotFound, gin.H{"error": "version not found"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get version"})
		return
	}
	c.JSON(http.StatusOK, v)
}

// ── Clone ────────────────────────────────────

// Clone deep-copies a workflow and all its stages.
// POST /api/v1/workflows/:id/clone
func (h *WorkflowHandler) Clone(c *gin.Context) {
	ctx := c.Request.Context()
	srcID := c.Param("id")

	src, err := h.stores.Workflows.GetByID(ctx, srcID)
	if err != nil {
		if err == sql.ErrNoRows {
			c.JSON(http.StatusNotFound, gin.H{"error": "workflow not found"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load workflow"})
		return
	}

	stages, err := h.stores.Workflows.ListStages(ctx, srcID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load stages"})
		return
	}

	clone := &models.Workflow{
		TeamID:                src.TeamID,
		Name:                  "Copy of " + src.Name,
		Slug:                  src.Slug + "-copy",
		Description:           src.Description,
		Branding:              src.Branding,
		EntryMode:             src.EntryMode,
		IsActive:              false,
		Version:               0,
		OnComplete:            src.OnComplete,
		Retention:             src.Retention,
		WebhookURL:            src.WebhookURL,
		WebhookSecret:         src.WebhookSecret,
		StalenessTimeoutHours: src.StalenessTimeoutHours,
		CreatedBy:             c.GetString("user_id"),
	}

	// v0.31.2: team-scoped route injects force_team_id
	if ftid, ok := c.Get("force_team_id"); ok {
		tid := ftid.(string)
		clone.TeamID = &tid
	}

	if err := h.stores.Workflows.Create(ctx, clone); err != nil {
		if strings.Contains(err.Error(), "unique") || strings.Contains(err.Error(), "UNIQUE") {
			// Slug collision — append short suffix
			clone.Slug = src.Slug + "-copy-" + store.NewID()[:6]
			if err2 := h.stores.Workflows.Create(ctx, clone); err2 != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create clone: " + err2.Error()})
				return
			}
		} else {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create clone: " + err.Error()})
			return
		}
	}

	for _, st := range stages {
		cloneSt := &models.WorkflowStage{
			WorkflowID:       clone.ID,
			Ordinal:          st.Ordinal,
			Name:             st.Name,
			AssignmentTeamID: st.AssignmentTeamID,
			FormTemplate:     st.FormTemplate,
			StageMode:        st.StageMode,
			Audience:         st.Audience,
			StageType:        st.StageType,
			AutoTransition:   st.AutoTransition,
			StageConfig:      st.StageConfig,
			BranchRules:      st.BranchRules,
			StarlarkHook:     st.StarlarkHook,
			SurfacePkgID:     st.SurfacePkgID,
			SLASeconds:       st.SLASeconds,
		}
		if err := h.stores.Workflows.CreateStage(ctx, cloneSt); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to clone stage: " + err.Error()})
			return
		}
	}

	clonedStages, _ := h.stores.Workflows.ListStages(ctx, clone.ID)
	if clonedStages == nil {
		clonedStages = []models.WorkflowStage{}
	}
	clone.Stages = clonedStages
	c.JSON(http.StatusCreated, clone)
}

// ── Helpers ─────────────────────────────────

func slugify(name string) string {
	s := strings.ToLower(strings.TrimSpace(name))
	s = regexp.MustCompile(`[^a-z0-9]+`).ReplaceAllString(s, "-")
	s = strings.Trim(s, "-")
	if len(s) > 64 {
		s = s[:64]
	}
	return s
}
