package tools

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"time"

	"chat-switchboard/models"
	"chat-switchboard/store"
	"chat-switchboard/webhook"
	"chat-switchboard/workflow"
)

// ── Late Registration ────────────────────────

// RegisterWorkflowTools registers the workflow_advance tool.
// Called from main.go after stores are initialized.
func RegisterWorkflowTools(stores store.Stores) {
	if stores.Workflows == nil {
		log.Println("⚠ workflow tools: WorkflowStore not available, skipping registration")
		return
	}
	Register(&workflowAdvanceTool{stores: stores})
	Register(&workflowRouteTool{stores: stores})
	log.Println("✅ workflow tools registered (workflow_advance, workflow_route)")
}

// ═══════════════════════════════════════════
// workflow_advance
// ═══════════════════════════════════════════
//
// Called by the stage persona when it has collected all required form
// data from the visitor. Triggers a stage transition — same effect as
// the human-triggered POST /channels/:id/workflow/advance endpoint.

type workflowAdvanceTool struct {
	BaseTool
	stores store.Stores
}

// Override availability: only available inside workflow channels.
func (t *workflowAdvanceTool) Availability() Require { return RequireWorkflow }

func (t *workflowAdvanceTool) Definition() ToolDef {
	return ToolDef{
		Name:        "workflow_advance",
		DisplayName: "Advance Workflow",
		Category:    "workflow",
		Description: "Advance the workflow to the next stage. Call this when you have " +
			"collected all required information from the visitor. Pass the collected " +
			"data as a JSON object — it will be saved and made available to the next stage.",
		Parameters: JSONSchema(map[string]interface{}{
			"data": map[string]interface{}{
				"type":        "object",
				"description": "Collected form data as key-value pairs. Must include all fields defined in the stage's form template.",
			},
			"summary": Prop("string", "Brief summary of what was collected (1-2 sentences, shown in stage notes)"),
		}, []string{"data"}),
	}
}

func (t *workflowAdvanceTool) Execute(ctx context.Context, execCtx ExecutionContext, argsJSON string) (string, error) {
	var args struct {
		Data    json.RawMessage `json:"data"`
		Summary string          `json:"summary"`
	}
	if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
		return "", fmt.Errorf("invalid arguments: %w", err)
	}

	if len(args.Data) == 0 || string(args.Data) == "null" {
		return "", fmt.Errorf("data is required")
	}

	channelID := execCtx.ChannelID
	if channelID == "" {
		return "", fmt.Errorf("no channel context")
	}

	// Read current workflow state
	ws, err := t.stores.Channels.GetWorkflowStatus(ctx, channelID)
	if err != nil || ws == nil || ws.WorkflowID == nil {
		return "", fmt.Errorf("not a workflow channel")
	}
	workflowID := *ws.WorkflowID
	currentStage := ws.CurrentStage
	status := ws.Status

	if status != "active" {
		return "", fmt.Errorf("workflow is %s, cannot advance", status)
	}

	// Load stages
	stages, err := t.stores.Workflows.ListStages(ctx, workflowID)
	if err != nil {
		return "", fmt.Errorf("failed to load stages: %w", err)
	}

	// Merge collected data into stage_data
	mergedData := MergeWorkflowStageData(ctx, t.stores.Channels, channelID, args.Data)
	nextStage, err := workflow.ResolveNextStage(stages, currentStage, json.RawMessage(mergedData))
	if err != nil {
		return "", fmt.Errorf("routing error: %w", err)
	}

	if nextStage >= len(stages) {
		// Workflow complete
		err = t.stores.Channels.CompleteWorkflow(ctx, channelID, nextStage, json.RawMessage(mergedData))
		if err != nil {
			return "", fmt.Errorf("failed to complete workflow: %w", err)
		}

		// Create note with collected data
		CreateWorkflowStageNote(ctx, t.stores, channelID, currentStage, args.Data, args.Summary)

		// v0.27.3: Fire on_complete chaining + webhook (was missing from tool path)
		go TriggerWorkflowOnComplete(ctx, t.stores, workflowID, channelID, mergedData)

		result, _ := json.Marshal(map[string]interface{}{
			"status":        "completed",
			"current_stage": nextStage,
			"message":       "Workflow completed successfully.",
		})
		return string(result), nil
	}

	// Advance to next stage
	err = t.stores.Channels.AdvanceWorkflowStage(ctx, channelID, nextStage, json.RawMessage(mergedData))
	if err != nil {
		return "", fmt.Errorf("failed to advance: %w", err)
	}

	// Create note with collected data
	CreateWorkflowStageNote(ctx, t.stores, channelID, currentStage, args.Data, args.Summary)

	// Create assignment if next stage has an assignment team
	nextStageDef := stages[nextStage]
	if nextStageDef.AssignmentTeamID != nil {
		CreateWorkflowAssignment(ctx, t.stores, channelID, nextStage, *nextStageDef.AssignmentTeamID)
	}

	msg := fmt.Sprintf("Advanced to stage %d: %s", nextStage, nextStageDef.Name)
	if nextStageDef.StageMode == models.StageModeFormOnly {
		msg += " (form-only stage — visitor will fill out a form, no chat needed)"
	}

	result, _ := json.Marshal(map[string]interface{}{
		"status":        "active",
		"current_stage": nextStage,
		"stage_name":    nextStageDef.Name,
		"stage_mode":    nextStageDef.StageMode,
		"message":       msg,
	})
	return string(result), nil
}

// ═══════════════════════════════════════════
// workflow_route (v0.35.0)
// ═══════════════════════════════════════════
//
// Called by a stage persona to route the workflow to a specific named stage.
// Enables AI-triggered routing (escalation, correction loops) and
// conditional branching driven by persona judgment.

type workflowRouteTool struct {
	BaseTool
	stores store.Stores
}

func (t *workflowRouteTool) Availability() Require { return RequireWorkflow }

func (t *workflowRouteTool) Definition() ToolDef {
	return ToolDef{
		Name:        "workflow_route",
		DisplayName: "Route Workflow",
		Category:    "workflow",
		Description: "Route the workflow to a specific stage by name. Use this when the " +
			"conversation should jump to a particular stage instead of advancing sequentially. " +
			"Common patterns: escalation to human review, loop back for correction, " +
			"skip stages based on collected information.",
		Parameters: JSONSchema(map[string]interface{}{
			"target_stage": Prop("string", "Name of the stage to route to (case-insensitive)."),
			"reason":       Prop("string", "Brief explanation of why this routing decision was made."),
		}, []string{"target_stage", "reason"}),
	}
}

func (t *workflowRouteTool) Execute(ctx context.Context, execCtx ExecutionContext, argsJSON string) (string, error) {
	var args struct {
		TargetStage string `json:"target_stage"`
		Reason      string `json:"reason"`
	}
	if err := json.Unmarshal([]byte(argsJSON), &args); err != nil {
		return "", fmt.Errorf("invalid arguments: %w", err)
	}
	if args.TargetStage == "" {
		return "", fmt.Errorf("target_stage is required")
	}

	channelID := execCtx.ChannelID
	if channelID == "" {
		return "", fmt.Errorf("no channel context")
	}

	ws, err := t.stores.Channels.GetWorkflowStatus(ctx, channelID)
	if err != nil || ws == nil || ws.WorkflowID == nil {
		return "", fmt.Errorf("not a workflow channel")
	}
	if ws.Status != "active" {
		return "", fmt.Errorf("workflow is %s, cannot route", ws.Status)
	}

	stages, err := t.stores.Workflows.ListStages(ctx, *ws.WorkflowID)
	if err != nil {
		return "", fmt.Errorf("failed to load stages: %w", err)
	}

	targetOrdinal, err := workflow.ResolveStageByName(stages, args.TargetStage)
	if err != nil {
		return "", fmt.Errorf("cannot resolve target: %w", err)
	}

	// Record routing decision in stage_data
	routeEntry, _ := json.Marshal(map[string]any{
		"from": ws.CurrentStage, "to": targetOrdinal,
		"reason": args.Reason, "ts": time.Now().UTC().Format(time.RFC3339),
	})
	historyPatch, _ := json.Marshal(map[string]any{
		"_route_history_latest": json.RawMessage(routeEntry),
	})
	mergedData := MergeWorkflowStageData(ctx, t.stores.Channels, channelID, json.RawMessage(historyPatch))

	if targetOrdinal >= len(stages) {
		// Route beyond last stage = complete
		err = t.stores.Channels.CompleteWorkflow(ctx, channelID, targetOrdinal, json.RawMessage(mergedData))
		if err != nil {
			return "", fmt.Errorf("failed to complete workflow: %w", err)
		}
		go TriggerWorkflowOnComplete(ctx, t.stores, *ws.WorkflowID, channelID, mergedData)
		result, _ := json.Marshal(map[string]any{
			"status": "completed", "message": "Workflow completed via routing.",
		})
		return string(result), nil
	}

	err = t.stores.Channels.AdvanceWorkflowStage(ctx, channelID, targetOrdinal, json.RawMessage(mergedData))
	if err != nil {
		return "", fmt.Errorf("failed to route: %w", err)
	}

	targetDef := stages[targetOrdinal]

	// Create assignment if target stage has an assignment team
	if targetDef.AssignmentTeamID != nil {
		CreateWorkflowAssignment(ctx, t.stores, channelID, targetOrdinal, *targetDef.AssignmentTeamID)
	}

	result, _ := json.Marshal(map[string]any{
		"status":        "active",
		"current_stage": targetOrdinal,
		"stage_name":    targetDef.Name,
		"stage_mode":    targetDef.StageMode,
		"message":       fmt.Sprintf("Routed to stage %d: %s (reason: %s)", targetOrdinal, targetDef.Name, args.Reason),
	})
	return string(result), nil
}

// ── Shared helpers (used by both tool and handler) ─

// MergeWorkflowStageData reads current stage_data, merges new data in Go, returns JSON string.
// v0.29.0: channelStore parameter replaces raw database.DB access.
func MergeWorkflowStageData(ctx context.Context, channelStore store.ChannelStore, channelID string, newData json.RawMessage) string {
	existing, _ := channelStore.GetStageData(ctx, channelID)

	if len(newData) == 0 || string(newData) == "null" {
		if len(existing) == 0 {
			return "{}"
		}
		return string(existing)
	}

	var base map[string]interface{}
	if err := json.Unmarshal(existing, &base); err != nil {
		base = map[string]interface{}{}
	}
	var incoming map[string]interface{}
	if err := json.Unmarshal(newData, &incoming); err == nil {
		for k, v := range incoming {
			base[k] = v
		}
	}
	merged, _ := json.Marshal(base)
	return string(merged)
}

// CreateWorkflowStageNote persists collected form data as a channel-scoped note.
func CreateWorkflowStageNote(ctx context.Context, stores store.Stores, channelID string, stageOrdinal int, data json.RawMessage, summary string) {
	if stores.Notes == nil || len(data) == 0 || string(data) == "null" {
		return
	}

	title := fmt.Sprintf("Stage %d collected data", stageOrdinal)
	if summary != "" {
		title = fmt.Sprintf("Stage %d: %s", stageOrdinal, summary)
	}

	content := string(data)

	// Find workflow channel owner for the note's user_id
	ch, err := stores.Channels.GetByID(ctx, channelID)
	if err != nil || ch == nil {
		return
	}
	userID := ch.UserID
	if userID == "" {
		return
	}

	note := &models.Note{
		Title:           title,
		Content:         content,
		SourceChannelID: &channelID,
	}
	note.UserID = userID

	if err := stores.Notes.Create(ctx, note); err != nil {
		log.Printf("⚠️ Failed to create stage note: %v", err)
	}
}

// CreateWorkflowAssignment creates an unassigned workflow assignment entry.
// Returns the assignment ID (for round-robin auto-assignment).
// v0.29.0: accepts stores parameter, delegates to WorkflowStore.
func CreateWorkflowAssignment(ctx context.Context, stores store.Stores, channelID string, stage int, teamID string) string {
	a := &store.WorkflowAssignment{
		ChannelID: channelID,
		Stage:     stage,
		TeamID:    teamID,
	}
	if err := stores.Workflows.CreateAssignment(ctx, a); err != nil {
		log.Printf("⚠️ Failed to create workflow assignment: %v", err)
		return ""
	}
	return a.ID
}

// ── Workflow Completion Triggers (v0.27.3) ──

// TriggerWorkflowOnComplete checks if the workflow has an on_complete chain
// config and fires the target workflow. Also delivers webhook if configured.
// Shared by both the HTTP handler (Advance endpoint) and the workflow_advance tool.
func TriggerWorkflowOnComplete(ctx context.Context, stores store.Stores, workflowID, channelID, mergedData string) {
	if stores.Workflows == nil {
		return
	}
	wf, err := stores.Workflows.GetByID(ctx, workflowID)
	if err != nil || wf == nil {
		return
	}

	// v0.27.3: Webhook delivery on workflow completion
	if wf.WebhookURL != "" {
		var stageData any
		_ = json.Unmarshal([]byte(mergedData), &stageData)

		go deliverWorkflowWebhook(wf.WebhookURL, wf.WebhookSecret, workflowID, channelID, stageData)
	}

	// on_complete chaining
	if len(wf.OnComplete) == 0 || string(wf.OnComplete) == "null" {
		return
	}

	var chain struct {
		Action     string            `json:"action"`
		TargetSlug string            `json:"target_slug"`
		DataMap    map[string]string `json:"data_map"`
	}
	if err := json.Unmarshal(wf.OnComplete, &chain); err != nil || chain.Action != "start_workflow" || chain.TargetSlug == "" {
		return
	}

	target, err := stores.Workflows.GetBySlug(ctx, wf.TeamID, chain.TargetSlug)
	if err != nil || target == nil || !target.IsActive {
		log.Printf("[workflow] on_complete: target workflow '%s' not found or inactive", chain.TargetSlug)
		return
	}

	var sourceData map[string]any
	if err := json.Unmarshal([]byte(mergedData), &sourceData); err != nil {
		sourceData = map[string]any{}
	}
	targetData := map[string]any{}
	if len(chain.DataMap) > 0 {
		for srcKey, tgtKey := range chain.DataMap {
			if v, ok := sourceData[srcKey]; ok {
				targetData[tgtKey] = v
			}
		}
	} else {
		targetData = sourceData
	}

	ch2, err2 := stores.Channels.GetByID(ctx, channelID)
	if err2 != nil || ch2 == nil {
		return
	}
	ownerID := ch2.UserID
	if ownerID == "" {
		return
	}

	ver, err := stores.Workflows.GetLatestVersion(ctx, target.ID)
	if err != nil {
		log.Printf("[workflow] on_complete: target '%s' has no published version", chain.TargetSlug)
		return
	}
	stages, err := stores.Workflows.ListStages(ctx, target.ID)
	if err != nil || len(stages) == 0 {
		return
	}

	ch := &models.Channel{
		UserID:      ownerID,
		Title:       target.Name + " (chained)",
		Description: target.Description,
		Type:        "workflow",
		TeamID:      target.TeamID,
	}
	if err := stores.Channels.Create(ctx, ch); err != nil {
		log.Printf("[workflow] on_complete: failed to create chained channel: %v", err)
		return
	}

	initialData, _ := json.Marshal(targetData)
	err = stores.Channels.SetWorkflowInstance(ctx, ch.ID, target.ID, ver.VersionNumber, initialData, "active")
	if err != nil {
		log.Printf("[workflow] on_complete: failed to set workflow columns: %v", err)
		return
	}
	_ = stores.Channels.Update(ctx, ch.ID, map[string]interface{}{"ai_mode": "auto"})

	// Bind first stage persona
	if len(stages) > 0 && stages[0].PersonaID != nil {
		_ = stores.Channels.AddParticipant(ctx, &models.ChannelParticipant{
			ChannelID:       ch.ID,
			ParticipantType: "persona",
			ParticipantID:   *stages[0].PersonaID,
			Role:            "member",
		})
	}

	log.Printf("[workflow] on_complete: started chained workflow %s → %s (channel %s)",
		workflowID, target.ID, ch.ID)
}

// deliverWorkflowWebhook fires a webhook for workflow completion.
func deliverWorkflowWebhook(url, secret, workflowID, channelID string, stageData any) {
	webhook.Deliver(url, secret, webhook.Payload{
		WorkflowID:  workflowID,
		ChannelID:   channelID,
		Status:      "completed",
		CompletedAt: time.Now().UTC(),
		StageData:   stageData,
	})
}

