package action

import (
	"encoding/json"
	"fmt"
	"regexp"
	"strings"

	"github.com/rockliang/poimen/workflows/pkg/db"
)

// IncompatibilityWarning explains why two activities can't be connected
type IncompatibilityWarning struct {
	Source       string `json:"source"`        // Source node ID
	Target       string `json:"target"`        // Target node ID
	Reason       string `json:"reason"`        // Why they can't connect
	SourceNeeds  string `json:"source_needs"`  // What source would need to output
	TargetNeeds  string `json:"target_needs"`  // What target requires as input
	Suggestion   string `json:"suggestion"`    // Suggestion to make it work
}

// ActivitySchema describes what an activity needs/provides
type ActivitySchema struct {
	ActivityType string                 `json:"activity_type"`
	Inputs       map[string]InputField  `json:"inputs"`
	Outputs      map[string]OutputField `json:"outputs"`
}

type InputField struct {
	Type        string   `json:"type"`
	Description string   `json:"description"`
	Required    bool     `json:"required"`
	Enum        []string `json:"enum,omitempty"`
}

type OutputField struct {
	Type        string `json:"type"`
	Description string `json:"description"`
}

// getActivitySchema returns schema from knowledge base
func getActivitySchema(activityType string) (*ActivitySchema, error) {
	kb := knowledgeBaseData()
	if kb == nil {
		return nil, fmt.Errorf("knowledge base not loaded")
	}

	var activities []map[string]interface{}
	if err := json.Unmarshal([]byte(kb), &activities); err != nil {
		// Try to extract activities from full KB structure
		var fullKB map[string]interface{}
		if err := json.Unmarshal([]byte(kb), &fullKB); err != nil {
			return nil, fmt.Errorf("failed to parse knowledge base")
		}
		if activitiesRaw, ok := fullKB["activities"]; ok {
			if b, err := json.Marshal(activitiesRaw); err == nil {
				if err := json.Unmarshal(b, &activities); err != nil {
					return nil, fmt.Errorf("failed to extract activities from KB")
				}
			}
		}
	}

	// Find matching activity
	for _, act := range activities {
		if name, ok := act["name"].(string); ok {
			if toActivityName(activityType) == name {
				// Convert to ActivitySchema
				schema := &ActivitySchema{
					ActivityType: activityType,
					Inputs:       make(map[string]InputField),
					Outputs:      make(map[string]OutputField),
				}

				if inputs, ok := act["inputs"].(map[string]interface{}); ok {
					for key, val := range inputs {
						if field, ok := val.(map[string]interface{}); ok {
							schema.Inputs[key] = parseInputField(field)
						}
					}
				}

				if outputs, ok := act["outputs"].(map[string]interface{}); ok {
					for key, val := range outputs {
						if field, ok := val.(map[string]interface{}); ok {
							schema.Outputs[key] = parseOutputField(field)
						}
					}
				}

				return schema, nil
			}
		}
	}

	return nil, fmt.Errorf("activity %s not found in knowledge base", activityType)
}

func parseInputField(data map[string]interface{}) InputField {
	field := InputField{}
	if t, ok := data["type"].(string); ok {
		field.Type = t
	}
	if d, ok := data["description"].(string); ok {
		field.Description = d
	}
	if r, ok := data["required"].(bool); ok {
		field.Required = r
	}
	return field
}

func parseOutputField(data map[string]interface{}) OutputField {
	field := OutputField{}
	if t, ok := data["type"].(string); ok {
		field.Type = t
	}
	if d, ok := data["description"].(string); ok {
		field.Description = d
	}
	return field
}

// CheckConnectionCompatibility validates if source can connect to target
func CheckConnectionCompatibility(sourceNode, targetNode db.WorkflowNode) []IncompatibilityWarning {
	warnings := []IncompatibilityWarning{}

	sourceSchema, err := getActivitySchema(sourceNode.Type)
	if err != nil {
		warnings = append(warnings, IncompatibilityWarning{
			Source:     sourceNode.ID,
			Target:     targetNode.ID,
			Reason:     fmt.Sprintf("Source activity schema not found: %v", err),
			Suggestion: "Ensure source activity type is registered in knowledge base",
		})
		return warnings
	}

	targetSchema, err := getActivitySchema(targetNode.Type)
	if err != nil {
		warnings = append(warnings, IncompatibilityWarning{
			Source:     sourceNode.ID,
			Target:     targetNode.ID,
			Reason:     fmt.Sprintf("Target activity schema not found: %v", err),
			Suggestion: "Ensure target activity type is registered in knowledge base",
		})
		return warnings
	}

	// Check if source produces outputs that target can consume
	if len(sourceSchema.Outputs) == 0 {
		warnings = append(warnings, IncompatibilityWarning{
			Source:     sourceNode.ID,
			Target:     targetNode.ID,
			Reason:     fmt.Sprintf("%s produces no outputs", sourceNode.Type),
			SourceNeeds: "any output",
			Suggestion: "Source activity must produce outputs",
		})
		return warnings
	}

	if len(targetSchema.Inputs) == 0 {
		warnings = append(warnings, IncompatibilityWarning{
			Source:     sourceNode.ID,
			Target:     targetNode.ID,
			Reason:     fmt.Sprintf("%s accepts no inputs", targetNode.Type),
			TargetNeeds: "no input",
			Suggestion: "Target activity must accept inputs. Check if it's a terminal activity.",
		})
		return warnings
	}

	// Match outputs to inputs
	sourceOutputs := getOutputNames(sourceSchema.Outputs)
	targetInputs := getInputNames(targetSchema.Inputs)

	if len(sourceOutputs) == 0 || len(targetInputs) == 0 {
		warnings = append(warnings, IncompatibilityWarning{
			Source:       sourceNode.ID,
			Target:       targetNode.ID,
			Reason:       "No compatible output/input fields found",
			SourceNeeds:  strings.Join(sourceOutputs, ", "),
			TargetNeeds:  strings.Join(targetInputs, ", "),
			Suggestion:   "Use LLM transformation to map outputs to inputs",
		})
	}

	return warnings
}

// CheckCanvasConnectivity analyzes all suggested edges for compatibility
func CheckCanvasConnectivity(nodes []db.WorkflowNode, suggestedEdges []db.WorkflowEdge) []IncompatibilityWarning {
	warnings := []IncompatibilityWarning{}
	nodeMap := make(map[string]db.WorkflowNode)
	for _, n := range nodes {
		nodeMap[n.ID] = n
	}

	for _, edge := range suggestedEdges {
		sourceNode, ok := nodeMap[edge.Source]
		if !ok {
			continue
		}
		targetNode, ok := nodeMap[edge.Target]
		if !ok {
			continue
		}

		edgeWarnings := CheckConnectionCompatibility(sourceNode, targetNode)
		warnings = append(warnings, edgeWarnings...)
	}

	return warnings
}

// IdentifyDisconnectedNodes finds nodes that can't connect to anything
func IdentifyDisconnectedNodes(nodes []db.WorkflowNode, suggestedEdges []db.WorkflowEdge) []string {
	edgeMap := make(map[string]bool)
	for _, edge := range suggestedEdges {
		edgeMap[edge.Source] = true
		edgeMap[edge.Target] = true
	}

	var disconnected []string
	for _, node := range nodes {
		if !edgeMap[node.ID] {
			disconnected = append(disconnected, node.ID)
		}
	}
	return disconnected
}

// toActivityName converts canvas type to activity name (e.g., "clone-repo" -> "CloneRepoActivity")
func toActivityName(canvasType string) string {
	parts := strings.Split(canvasType, "-")
	var result string
	for _, part := range parts {
		if part != "" {
			result += strings.ToUpper(part[:1]) + strings.ToLower(part[1:])
		}
	}
	return result + "Activity"
}

// getOutputNames extracts output field names
func getOutputNames(outputs map[string]OutputField) []string {
	var names []string
	for name := range outputs {
		names = append(names, name)
	}
	return names
}

// getInputNames extracts input field names (required ones highlighted)
func getInputNames(inputs map[string]InputField) []string {
	var names []string
	for name, field := range inputs {
		if field.Required {
			names = append(names, name+"*")
		} else {
			names = append(names, name)
		}
	}
	return names
}

// SuggestDataTransformation proposes how to connect incompatible activities
func SuggestDataTransformation(sourceNode, targetNode db.WorkflowNode) string {
	sourceSchema, _ := getActivitySchema(sourceNode.Type)
	targetSchema, _ := getActivitySchema(targetNode.Type)

	if sourceSchema == nil || targetSchema == nil {
		return "Cannot analyze compatibility without schemas"
	}

	sourceOuts := getOutputNames(sourceSchema.Outputs)
	targetIns := getInputNames(targetSchema.Inputs)

	return fmt.Sprintf(
		"To connect %s → %s:\n"+
			"  %s outputs: %s\n"+
			"  %s needs: %s\n"+
			"  Solution: Use LLM transformation node to map outputs to inputs",
		sourceNode.Label, targetNode.Label,
		sourceNode.Type, strings.Join(sourceOuts, ", "),
		targetNode.Type, strings.Join(targetIns, ", "),
	)
}

// knowledgeBaseData returns raw KB JSON (stub - implement with actual KB loading)
func knowledgeBaseData() string {
	// This would load from activity_knowledge_base.json
	// For now, return empty - real implementation loads from file
	return ""
}

// CanvasCompatibilityInput for Temporal activity
type CanvasCompatibilityInput struct {
	Nodes []db.WorkflowNode `json:"nodes"`
	Edges []db.WorkflowEdge `json:"edges"`
}

// CanvasCompatibilityOutput returns validation results
type CanvasCompatibilityOutput struct {
	IsValid           bool                      `json:"is_valid"`
	Incompatibilities []IncompatibilityWarning  `json:"incompatibilities"`
	DisconnectedNodes []string                  `json:"disconnected_nodes"`
	Warnings          []string                  `json:"warnings"`
}

// CanvasCompatibilityActivity validates workflow canvas for type mismatches and isolation
func CanvasCompatibilityActivity(ctx interface{}, input CanvasCompatibilityInput) (CanvasCompatibilityOutput, error) {
	output := CanvasCompatibilityOutput{
		IsValid:           true,
		Incompatibilities: []IncompatibilityWarning{},
		DisconnectedNodes: []string{},
		Warnings:          []string{},
	}

	// Check all edges for compatibility
	for _, edge := range input.Edges {
		var sourceNode, targetNode *db.WorkflowNode
		for i := range input.Nodes {
			if input.Nodes[i].ID == edge.Source {
				sourceNode = &input.Nodes[i]
			}
			if input.Nodes[i].ID == edge.Target {
				targetNode = &input.Nodes[i]
			}
		}

		if sourceNode != nil && targetNode != nil {
			if warning, err := ValidateConnection(sourceNode, targetNode); err != nil {
				output.IsValid = false
				output.Incompatibilities = append(output.Incompatibilities, warning)
			}
		}
	}

	// Find disconnected nodes
	connected := make(map[string]bool)
	for _, edge := range input.Edges {
		connected[edge.Source] = true
		connected[edge.Target] = true
	}

	for _, node := range input.Nodes {
		if node.Type == "activity" && !connected[node.ID] {
			output.DisconnectedNodes = append(output.DisconnectedNodes, node.ID)
		}
	}

	return output, nil
}
