package handlers

import (
	"context"
	"encoding/json"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"

	"chat-switchboard/events"
	"chat-switchboard/models"
	"chat-switchboard/sandbox"
	"chat-switchboard/store"
	"chat-switchboard/tools"
	"chat-switchboard/workflow"

	"go.starlark.net/starlark"
)

// ── Workflow Form Handler ─────────────────────
// Handles typed form submission for workflow stages.

type WorkflowFormHandler struct {
	stores store.Stores
	runner *sandbox.Runner
	hub    *events.Hub
}

func NewWorkflowFormHandler(stores store.Stores, runner *sandbox.Runner, hub *events.Hub) *WorkflowFormHandler {
	return &WorkflowFormHandler{stores: stores, runner: runner, hub: hub}
}

// GetFormTemplate returns the current stage's typed form template.
// GET /api/v1/w/:id/form
func (h *WorkflowFormHandler) GetFormTemplate(c *gin.Context) {
	ctx := c.Request.Context()
	channelID := c.Param("id")

	ws, err := h.stores.Channels.GetWorkflowStatus(ctx, channelID)
	if err != nil || ws == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "workflow channel not found"})
		return
	}
	if ws.WorkflowID == nil || *ws.WorkflowID == "" {
		c.JSON(http.StatusNotFound, gin.H{"error": "not a workflow channel"})
		return
	}

	stages, err := h.stores.Workflows.ListStages(ctx, *ws.WorkflowID)
	if err != nil || ws.CurrentStage >= len(stages) {
		c.JSON(http.StatusNotFound, gin.H{"error": "stage not found"})
		return
	}

	stage := stages[ws.CurrentStage]
	tpl := models.ParseTypedFormTemplate(stage.FormTemplate)

	// Check if form was already submitted for this stage
	var formSubmitted bool
	if len(ws.StageData) > 0 {
		var sd map[string]interface{}
		if json.Unmarshal(ws.StageData, &sd) == nil {
			_, formSubmitted = sd["_form_submitted"]
		}
	}

	c.JSON(http.StatusOK, gin.H{
		"stage_name":     stage.Name,
		"stage_mode":     stage.StageMode,
		"form_template":  tpl,
		"form_submitted": formSubmitted,
		"current_stage":  ws.CurrentStage,
		"status":         ws.Status,
	})
}

// SubmitForm handles form data submission for a workflow stage.
// POST /api/v1/w/:id/form-submit
func (h *WorkflowFormHandler) SubmitForm(c *gin.Context) {
	ctx := c.Request.Context()
	channelID := c.Param("id")

	// 1. Verify workflow channel
	ws, err := h.stores.Channels.GetWorkflowStatus(ctx, channelID)
	if err != nil || ws == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "workflow channel not found"})
		return
	}
	if ws.Status != "active" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "workflow is " + ws.Status + ", cannot submit form"})
		return
	}
	if ws.WorkflowID == nil || *ws.WorkflowID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "not a workflow channel"})
		return
	}

	// 2. Load stage definition
	stages, err := h.stores.Workflows.ListStages(ctx, *ws.WorkflowID)
	if err != nil || ws.CurrentStage >= len(stages) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "stage not found"})
		return
	}
	stage := stages[ws.CurrentStage]

	if stage.StageMode == models.StageModeChatOnly {
		c.JSON(http.StatusBadRequest, gin.H{"error": "this stage does not accept form submissions"})
		return
	}

	// 3. Parse typed form template
	tpl := models.ParseTypedFormTemplate(stage.FormTemplate)
	if tpl == nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "stage has no typed form template"})
		return
	}

	// 4. Parse submitted data (flat fields per ICD spec)
	var formData map[string]interface{}
	if err := c.ShouldBindJSON(&formData); err != nil || len(formData) == 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "form data is required"})
		return
	}

	// 5. Go-level validation
	if errs := models.ValidateFormData(tpl, formData); len(errs) > 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "validation failed", "errors": errs})
		return
	}

	// 6. Starlark validate hook (if configured)
	if tpl.Hooks != nil && tpl.Hooks.PackageID != "" && tpl.Hooks.Validate != "" {
		if hookErrs := h.runValidateHook(c, tpl.Hooks, channelID, formData, ws.StageData, stage.Name); hookErrs != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "validation failed", "errors": hookErrs})
			return
		}
	}

	// 7. Merge into stage_data
	formData["_form_submitted"] = true
	dataJSON, _ := json.Marshal(formData)
	mergedData := tools.MergeWorkflowStageData(ctx, h.stores.Channels, channelID, dataJSON)

	// 8. Starlark on_submit hook (fire-and-forget)
	if tpl.Hooks != nil && tpl.Hooks.PackageID != "" && tpl.Hooks.OnSubmit != "" {
		go h.runOnSubmitHook(tpl.Hooks, channelID, formData, json.RawMessage(mergedData), stage.Name)
	}

	// 9. Auto-advance if form_only + auto_transition
	if stage.StageMode == models.StageModeFormOnly && stage.AutoTransition {
		nextStage, _ := workflow.ResolveNextStage(stages, ws.CurrentStage, json.RawMessage(mergedData))
		if nextStage >= len(stages) {
			// Complete
			_ = h.stores.Channels.CompleteWorkflow(ctx, channelID, nextStage, json.RawMessage(mergedData))
			tools.CreateWorkflowStageNote(ctx, h.stores, channelID, ws.CurrentStage, dataJSON, "")
			h.emitFormEvent(channelID, "workflow.completed", ws.CurrentStage)
			c.JSON(http.StatusOK, gin.H{"status": "completed", "current_stage": nextStage})
			return
		}
		_ = h.stores.Channels.AdvanceWorkflowStage(ctx, channelID, nextStage, json.RawMessage(mergedData))
		tools.CreateWorkflowStageNote(ctx, h.stores, channelID, ws.CurrentStage, dataJSON, "")
		h.emitFormEvent(channelID, "workflow.advanced", nextStage)
		c.JSON(http.StatusOK, gin.H{"status": "advanced", "current_stage": nextStage, "stage": stages[nextStage]})
		return
	}

	// 10. Not auto-advancing — persist stage_data and confirm submission
	_ = h.stores.Channels.AdvanceWorkflowStage(ctx, channelID, ws.CurrentStage, json.RawMessage(mergedData))
	tools.CreateWorkflowStageNote(ctx, h.stores, channelID, ws.CurrentStage, dataJSON, "")
	h.emitFormEvent(channelID, "workflow.form_submitted", ws.CurrentStage)
	c.JSON(http.StatusOK, gin.H{"status": "submitted", "current_stage": ws.CurrentStage})
}

// ── Starlark Hooks ──────────────────────────

func (h *WorkflowFormHandler) runValidateHook(c *gin.Context, hooks *models.FormHooks, channelID string, data map[string]interface{}, stageData json.RawMessage, stageName string) []models.FieldError {
	if h.runner == nil {
		return nil
	}

	pkg, err := h.stores.Packages.Get(c.Request.Context(), hooks.PackageID)
	if err != nil || pkg == nil {
		log.Printf("[workflow-forms] validate hook: package %s not found", hooks.PackageID)
		return nil
	}

	ctxDict := starlark.NewDict(4)
	_ = ctxDict.SetKey(starlark.String("data"), jsonToStarlark(data))
	_ = ctxDict.SetKey(starlark.String("stage_data"), starlark.String(string(stageData)))
	_ = ctxDict.SetKey(starlark.String("stage_name"), starlark.String(stageName))
	_ = ctxDict.SetKey(starlark.String("channel_id"), starlark.String(channelID))

	val, _, err := h.runner.CallEntryPoint(c.Request.Context(), pkg, hooks.Validate,
		starlark.Tuple{ctxDict}, nil, nil)
	if err != nil {
		log.Printf("[workflow-forms] validate hook error: %v", err)
		return nil
	}

	return parseValidationResult(val)
}

func (h *WorkflowFormHandler) runOnSubmitHook(hooks *models.FormHooks, channelID string, data map[string]interface{}, stageData json.RawMessage, stageName string) {
	if h.runner == nil {
		return
	}

	ctx := context.Background()
	pkg, err := h.stores.Packages.Get(ctx, hooks.PackageID)
	if err != nil || pkg == nil {
		return
	}

	ctxDict := starlark.NewDict(4)
	_ = ctxDict.SetKey(starlark.String("data"), jsonToStarlark(data))
	_ = ctxDict.SetKey(starlark.String("stage_data"), starlark.String(string(stageData)))
	_ = ctxDict.SetKey(starlark.String("stage_name"), starlark.String(stageName))
	_ = ctxDict.SetKey(starlark.String("channel_id"), starlark.String(channelID))

	_, _, err = h.runner.CallEntryPoint(ctx, pkg, hooks.OnSubmit,
		starlark.Tuple{ctxDict}, nil, nil)
	if err != nil {
		log.Printf("[workflow-forms] on_submit hook error: %v", err)
	}
}

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

func (h *WorkflowFormHandler) emitFormEvent(channelID, label string, stage int) {
	if h.hub == nil {
		return
	}
	payload, _ := json.Marshal(map[string]interface{}{
		"channel_id": channelID,
		"stage":      stage,
	})
	ctx := context.Background()
	pids, err := h.stores.Channels.ListUserParticipantIDs(ctx, channelID, "")
	if err != nil {
		return
	}
	evt := events.Event{Label: label, Payload: payload}
	for _, uid := range pids {
		h.hub.PublishToUser(uid, evt)
	}
}

// parseValidationResult extracts field errors from a Starlark return value.
// Expected: None (pass) or {"errors": [{"key": "...", "message": "..."}, ...]}
func parseValidationResult(val starlark.Value) []models.FieldError {
	if val == nil || val == starlark.None {
		return nil
	}
	d, ok := val.(*starlark.Dict)
	if !ok {
		return nil
	}
	errVal, found, _ := d.Get(starlark.String("errors"))
	if !found {
		return nil
	}
	list, ok := errVal.(*starlark.List)
	if !ok {
		return nil
	}
	var errs []models.FieldError
	iter := list.Iterate()
	defer iter.Done()
	var item starlark.Value
	for iter.Next(&item) {
		ed, ok := item.(*starlark.Dict)
		if !ok {
			continue
		}
		keyVal, _, _ := ed.Get(starlark.String("key"))
		msgVal, _, _ := ed.Get(starlark.String("message"))
		k, _ := keyVal.(starlark.String)
		m, _ := msgVal.(starlark.String)
		if k != "" && m != "" {
			errs = append(errs, models.FieldError{Key: string(k), Message: string(m)})
		}
	}
	if len(errs) == 0 {
		return nil
	}
	return errs
}
