package action

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

	"github.com/rockliang/poimen/workflows/action/llm"
	"github.com/rockliang/poimen/workflows/pkg/db"
	"github.com/rockliang/poimen/workflows/statemachine"
)

// CanvasReasonerInput infers connections between nodes using LLM reasoning
type CanvasReasonerInput struct {
	Nodes []db.WorkflowNode `json:"nodes"`       // Canvas nodes
	Edges []db.WorkflowEdge `json:"edges"`       // Existing edges
	// If true, only suggest new edges; if false, redesign entire canvas
	PreserveExisting bool `json:"preserve_existing,omitempty"`
	AuthToken        string `json:"auth_token,omitempty"` // JWT for LLM calls
}

// RelationWording describes semantic meaning of an edge
type RelationWording struct {
	Verb               string `json:"verb"`                 // outputs, inputs, depends-on, etc
	SourceOutput       string `json:"source_output"`        // What source produces
	TargetInput        string `json:"target_input"`         // What target requires
	ConnectionType     string `json:"connection_type"`      // direct-map, requires-transformer, conditional
	Confidence         float64 `json:"confidence"`          // 0.0-1.0
	SemanticMatch      string `json:"semantic_match"`       // Human-readable explanation
	TransformerNeeded  string `json:"transformer_needed,omitempty"` // If transformation required
}

// EdgeWithWording pairs an edge with its semantic description
type EdgeWithWording struct {
	Source           string          `json:"source"`
	Target           string          `json:"target"`
	RelationType     string          `json:"relation_type"`      // data-flow, dependency, conditional, parallel
	RelationLabel    string          `json:"relation_label"`     // e.g., "CloneRepo outputs path → AnalyzeCode requires path"
	RelationWording  RelationWording `json:"relation_wording"`
}

// CanvasReasonerOutput returns suggested edges and reasoning
type CanvasReasonerOutput struct {
	SuggestedEdges        []EdgeWithWording             `json:"suggested_edges"`         // Edges with wording
	RemovedEdges          []db.WorkflowEdge             `json:"removed_edges,omitempty"` // Edges to remove
	Reasoning             string                        `json:"reasoning"`               // LLM explanation
	Confidence            float64                       `json:"confidence"`              // 0.0-1.0
	IncompatibleEdges     []IncompatibilityWarning      `json:"incompatible_edges,omitempty"` // Can't connect
	DisconnectedNodes     []string                      `json:"disconnected_nodes,omitempty"` // No connections
	UserAlerts            []string                      `json:"user_alerts,omitempty"`        // Human-readable warnings
}

// CanvasReasonerActivity uses LLM to infer connections between workflow activities
func CanvasReasonerActivity(ctx context.Context, in CanvasReasonerInput) (CanvasReasonerOutput, error) {
	logger := newActivityLogger(ctx)

	output := CanvasReasonerOutput{
		SuggestedEdges: []db.WorkflowEdge{},
	}

	if len(in.Nodes) == 0 {
		return output, fmt.Errorf("no nodes provided")
	}

	logger.logf("info", "Analyzing canvas with %d nodes, %d edges", len(in.Nodes), len(in.Edges))

	// Build activity descriptions for LLM context
	nodeDesc := buildNodeDescriptions(in.Nodes)
	edgeDesc := buildEdgeDescriptions(in.Edges)

	// Create prompt for LLM reasoning with relation wording
	systemPrompt := `You are a workflow automation expert. Analyze activities and suggest logical connections with semantic descriptions.

CRITICAL RULES:
1. Only suggest edges where outputs→inputs match
2. Provide relation wording: verb, source_output, target_input
3. Assess connection confidence (0.0-1.0)
4. Flag type mismatches that need transformers

Respond with JSON:
{
  "edges": [
    {
      "source": "node-1",
      "target": "node-2",
      "relation_type": "data-flow|dependency|conditional|parallel",
      "relation_label": "Node1 outputs X → Node2 requires X",
      "relation_wording": {
        "verb": "outputs|depends-on|triggers|etc",
        "source_output": "field_name (type): description",
        "target_input": "field_name (type, required?): description",
        "connection_type": "direct-map|requires-transformer|conditional",
        "confidence": 0.95,
        "semantic_match": "Explanation of why this makes sense"
      }
    }
  ],
  "reasoning": "Overall workflow structure explanation",
  "confidence": 0.85
}`

	userPrompt := fmt.Sprintf(`Canvas Analysis:

Nodes (including inputs/outputs):
%s

Current Edges:
%s

Task: %s

KEY RULES:
- Preserve existing edges and suggest only NEW edges to add
- SKIP any connections where input/output types don't match
- If an activity has no outputs, it cannot be a source
- If an activity has no inputs, it cannot be a target
- Note any activities that are hard to connect (terminal activities, generators, etc)

Return ONLY valid JSON, no markdown code blocks.`, nodeDesc, edgeDesc, getReasoningTask(in.PreserveExisting))

	logger.logf("info", "Calling LLM reasoning (preserve_existing=%v)", in.PreserveExisting)

	// Call LLM
	client, err := llm.NewClient()
	if err != nil {
		return output, fmt.Errorf("failed to create LLM client: %w", err)
	}

	response, err := client.CreateMessage(ctx, llm.MessageInput{
		Model: statemachine.ModelSpec{
			ModelID: "reasoning", // Use reasoning model for complex analysis
		},
		SystemPrompt: systemPrompt,
		Messages: []llm.MessageParam{
			{
				Role:    "user",
				Content: userPrompt,
			},
		},
		AuthToken: in.AuthToken,
	})

	if err != nil {
		return output, fmt.Errorf("LLM reasoning failed: %w", err)
	}

	// Parse LLM response
	var reasonerResp struct {
		Edges      []db.WorkflowEdge `json:"edges"`
		Reasoning  string            `json:"reasoning"`
		Confidence float64           `json:"confidence"`
	}

	if err := json.Unmarshal([]byte(response), &reasonerResp); err != nil {
		logger.logf("warn", "Failed to parse LLM response as JSON: %v", err)
		// Try to extract from response text
		output.Reasoning = response
		output.Confidence = 0.5
		return output, fmt.Errorf("failed to parse LLM response: %w", err)
	}

	// Validate suggested edges
	nodeMap := make(map[string]bool)
	for _, n := range in.Nodes {
		nodeMap[n.ID] = true
	}

	validEdges := []db.WorkflowEdge{}
	for _, edge := range reasonerResp.Edges {
		if !nodeMap[edge.Source] {
			logger.logf("warn", "Suggested edge references unknown source: %s", edge.Source)
			continue
		}
		if !nodeMap[edge.Target] {
			logger.logf("warn", "Suggested edge references unknown target: %s", edge.Target)
			continue
		}
		// Don't suggest self-loops
		if edge.Source == edge.Target {
			logger.logf("warn", "Skipping self-loop: %s", edge.Source)
			continue
		}
		validEdges = append(validEdges, edge)
	}

	output.SuggestedEdges = validEdges
	output.Reasoning = reasonerResp.Reasoning
	output.Confidence = reasonerResp.Confidence

	// Check compatibility of suggested edges
	incompatibilities := CheckCanvasConnectivity(in.Nodes, validEdges)
	if len(incompatibilities) > 0 {
		output.IncompatibleEdges = incompatibilities
		logger.logf("warn", "Found %d incompatible edge connections", len(incompatibilities))

		// Generate user-friendly alerts
		for i, incompat := range incompatibilities {
			if i < 5 { // Limit to 5 alerts to avoid spam
				alert := fmt.Sprintf(
					"⚠️  %s → %s: %s. %s",
					incompat.Source, incompat.Target, incompat.Reason, incompat.Suggestion,
				)
				output.UserAlerts = append(output.UserAlerts, alert)
			}
		}
	}

	// Identify disconnected nodes
	disconnected := IdentifyDisconnectedNodes(in.Nodes, validEdges)
	if len(disconnected) > 0 {
		output.DisconnectedNodes = disconnected
		logger.logf("warn", "Found %d disconnected nodes", len(disconnected))

		for _, nodeID := range disconnected {
			var label string
			for _, node := range in.Nodes {
				if node.ID == nodeID {
					label = node.Label
					break
				}
			}
			alert := fmt.Sprintf(
				"🔌 Node '%s' has no connections. Consider adding edges or removing it.",
				label,
			)
			output.UserAlerts = append(output.UserAlerts, alert)
		}
	}

	logger.logf("info", "LLM suggested %d edges with confidence %.2f | %d incompatibilities | %d disconnected",
		len(validEdges), output.Confidence, len(incompatibilities), len(disconnected))

	return output, nil
}

// buildNodeDescriptions creates readable node descriptions for LLM (including schemas)
func buildNodeDescriptions(nodes []db.WorkflowNode) string {
	var desc string
	for i, node := range nodes {
		desc += fmt.Sprintf("%d. [%s] %s (type: %s)\n", i+1, node.ID, node.Label, node.Type)

		// Add input/output schema info
		if schema, err := getActivitySchema(node.Type); err == nil {
			if len(schema.Inputs) > 0 {
				desc += fmt.Sprintf("   INPUTS: %v\n", getInputNames(schema.Inputs))
			} else {
				desc += fmt.Sprintf("   INPUTS: none (generator/trigger)\n")
			}
			if len(schema.Outputs) > 0 {
				desc += fmt.Sprintf("   OUTPUTS: %v\n", getOutputNames(schema.Outputs))
			} else {
				desc += fmt.Sprintf("   OUTPUTS: none (terminal/sink)\n")
			}
		}

		if node.Data != nil {
			if b, err := json.MarshalIndent(node.Data, "   ", "  "); err == nil {
				desc += fmt.Sprintf("   CONFIG: %s\n", string(b))
			}
		}
	}
	return desc
}

// buildEdgeDescriptions creates readable edge descriptions for LLM
func buildEdgeDescriptions(edges []db.WorkflowEdge) string {
	if len(edges) == 0 {
		return "None"
	}
	var desc string
	for i, edge := range edges {
		desc += fmt.Sprintf("%d. %s → %s\n", i+1, edge.Source, edge.Target)
	}
	return desc
}

// getReasoningTask returns task description based on preservation mode
func getReasoningTask(preserveExisting bool) string {
	if preserveExisting {
		return "Keep all existing edges and suggest ONLY NEW edges to improve workflow"
	}
	return "Design optimal workflow by suggesting all connections and noting any redundant edges"
}
