package metadata

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/metadatair"
)

// FlowComponentIRSource describes the source component that produced one flow
// metadata projection. It is provenance for neutral IR headers, not a runtime
// read path or a second metadata authority. The source coordinates point at the
// Flow metadata JSON payload; FlowDefinition and FlowVersion rows are context,
// not facts sourced from that payload.
type FlowComponentIRSource struct {
	Owner          metadatair.Owner
	URI            string
	Component      string
	Dialect        string
	DialectVersion string
	Annotations    map[string]string
}

// FlowElementIdentityProjection is the runtime-facing Flow element identity
// subset currently represented by neutral IR node headers.
type FlowElementIdentityProjection struct {
	VersionNodeID string
	ElementNames  []string
}

type flowElementHeader struct {
	name string
	path string
}

// LowerFlowComponentHeaders lowers one Flow metadata projection into neutral IR
// headers for executable Flow elements. FlowDefinition and FlowVersion provide
// the stable version identity prefix, but they are not emitted as Flow metadata
// JSON nodes because their authoritative facts live outside that source payload.
func LowerFlowComponentHeaders(definition *models.FlowDefinition, version *models.FlowVersion, source FlowComponentIRSource) (metadatair.Document, error) {
	definitionName, versionID, err := flowDefinitionVersionIdentity(definition, version)
	if err != nil {
		return metadatair.Document{}, err
	}
	if err := validateFlowComponentIRSource(source); err != nil {
		return metadatair.Document{}, err
	}

	elements, err := flowElementHeaders(version.Metadata)
	if err != nil {
		return metadatair.Document{}, fmt.Errorf("metadata IR flow %s version %s elements invalid: %w", definitionName, versionID, err)
	}

	nodes := make([]metadatair.NodeHeader, 0, len(elements))
	for _, element := range elements {
		nodes = append(nodes, flowComponentNode(source, metadatair.NodeKindFlowElement, flowElementNodeID(versionID, element.name), element.path))
	}

	doc := metadatair.Document{
		Version: neutralMetadataIRVersion(),
		Nodes:   nodes,
	}
	if err := doc.Validate(); err != nil {
		return metadatair.Document{}, err
	}
	return doc, nil
}

func flowDefinitionVersionIdentity(definition *models.FlowDefinition, version *models.FlowVersion) (string, metadatair.NodeID, error) {
	if definition == nil {
		return "", "", fmt.Errorf("metadata IR flow lowering requires flow definition")
	}
	if version == nil {
		return "", "", fmt.Errorf("metadata IR flow lowering requires flow version")
	}
	definitionName := strings.TrimSpace(definition.DeveloperName)
	if definitionName == "" {
		return "", "", fmt.Errorf("metadata IR flow lowering requires FlowDefinition DeveloperName")
	}
	if version.VersionNumber <= 0 {
		return "", "", fmt.Errorf("metadata IR flow %s lowering requires positive FlowVersion VersionNumber", definitionName)
	}
	if strings.TrimSpace(definition.Id) != "" &&
		strings.TrimSpace(version.DefinitionId) != "" &&
		strings.TrimSpace(definition.Id) != strings.TrimSpace(version.DefinitionId) {
		return "", "", fmt.Errorf("metadata IR flow %s version definition id %q does not match definition id %q", definitionName, version.DefinitionId, definition.Id)
	}
	return definitionName, flowVersionNodeID(definitionName, version.VersionNumber), nil
}

// ProjectFlowElementIdentityHeaders projects neutral Flow element headers back
// into the runtime-facing identity order represented by the P3 header slice.
func ProjectFlowElementIdentityHeaders(doc metadatair.Document, versionID metadatair.NodeID) (FlowElementIdentityProjection, error) {
	if err := doc.Validate(); err != nil {
		return FlowElementIdentityProjection{}, err
	}

	versionNodeID := strings.TrimSpace(string(versionID))
	if versionNodeID == "" {
		return FlowElementIdentityProjection{}, fmt.Errorf("metadata IR flow element identity projection requires a FlowVersion node id")
	}
	prefix := versionNodeID + "."
	out := FlowElementIdentityProjection{VersionNodeID: versionNodeID}
	for _, node := range doc.Nodes {
		if node.Kind != metadatair.NodeKindFlowElement {
			return FlowElementIdentityProjection{}, fmt.Errorf("metadata IR flow element identity projection requires only FlowElement nodes, got %s node %q", node.Kind, node.ID)
		}
		if node.Source.Format != metadatair.SourceFormatFlowMetadataJSON {
			return FlowElementIdentityProjection{}, fmt.Errorf("metadata IR flow element node %q source format %q is not %q", node.ID, node.Source.Format, metadatair.SourceFormatFlowMetadataJSON)
		}
		elementID := string(node.ID)
		elementName, ok := strings.CutPrefix(elementID, prefix)
		if !ok || elementName == "" {
			return FlowElementIdentityProjection{}, fmt.Errorf("metadata IR flow element node %q does not belong to version %s", elementID, versionNodeID)
		}
		out.ElementNames = append(out.ElementNames, elementName)
	}
	return out, nil
}

func flowVersionNodeID(definitionDeveloperName string, versionNumber int) metadatair.NodeID {
	return metadatair.NodeID(definitionDeveloperName + ".v" + strconv.Itoa(versionNumber))
}

func flowElementNodeID(versionID metadatair.NodeID, elementName string) metadatair.NodeID {
	return metadatair.NodeID(string(versionID) + "." + elementName)
}

func validateFlowComponentIRSource(source FlowComponentIRSource) error {
	if err := source.Owner.Validate(); err != nil {
		return fmt.Errorf("metadata IR flow source owner invalid: %w", err)
	}
	if err := flowSourceLocation(source, "/").Validate(); err != nil {
		return fmt.Errorf("metadata IR flow source invalid: %w", err)
	}
	return nil
}

func flowComponentNode(source FlowComponentIRSource, kind metadatair.NodeKind, id metadatair.NodeID, path string) metadatair.NodeHeader {
	return metadatair.NodeHeader{
		ID:       id,
		Kind:     kind,
		Owner:    source.Owner,
		Source:   flowSourceLocation(source, path),
		Boundary: flowMetadataLoweringBoundary(),
	}
}

func flowSourceLocation(source FlowComponentIRSource, path string) metadatair.SourceLocation {
	return metadatair.SourceLocation{
		Format:         metadatair.SourceFormatFlowMetadataJSON,
		URI:            source.URI,
		Component:      source.Component,
		Path:           path,
		Dialect:        source.Dialect,
		DialectVersion: source.DialectVersion,
		Annotations:    cloneIRAnnotations(source.Annotations),
	}
}

func flowMetadataLoweringBoundary() metadatair.LoweringBoundary {
	return metadatair.LoweringBoundary{
		From: metadatair.SourceFormatFlowMetadataJSON,
		To:   metadatair.TargetSurfaceNeutralIR,
	}
}

func flowElementHeaders(metadata models.FlowMetadata) ([]flowElementHeader, error) {
	out := make([]flowElementHeader, 0)
	appendNamed := func(section string, index int, name string) error {
		name = strings.TrimSpace(name)
		if name == "" {
			return fmt.Errorf("%s element name is required", section)
		}
		out = append(out, flowElementHeader{name: name, path: "/" + section + "/" + strconv.Itoa(index)})
		return nil
	}
	for i, element := range metadata.Decisions {
		if err := appendNamed("decisions", i, element.Name); err != nil {
			return nil, err
		}
	}
	for i, element := range metadata.Assignments {
		if err := appendNamed("assignments", i, element.Name); err != nil {
			return nil, err
		}
	}
	for i, element := range metadata.RecordCreates {
		if err := appendNamed("recordCreates", i, element.Name); err != nil {
			return nil, err
		}
	}
	for i, element := range metadata.RecordUpdates {
		if err := appendNamed("recordUpdates", i, element.Name); err != nil {
			return nil, err
		}
	}
	for i, element := range metadata.RecordDeletes {
		if err := appendNamed("recordDeletes", i, element.Name); err != nil {
			return nil, err
		}
	}
	for i, element := range metadata.RecordLookups {
		if err := appendNamed("recordLookups", i, element.Name); err != nil {
			return nil, err
		}
	}
	for i, element := range metadata.ActionCalls {
		if err := appendNamed("actionCalls", i, element.Name); err != nil {
			return nil, err
		}
	}
	for i, element := range metadata.Subflows {
		if err := appendNamed("subflows", i, element.Name); err != nil {
			return nil, err
		}
	}
	for i, element := range metadata.Loops {
		if err := appendNamed("loops", i, element.Name); err != nil {
			return nil, err
		}
	}
	for i, element := range metadata.CustomErrors {
		if err := appendNamed("customErrors", i, element.Name); err != nil {
			return nil, err
		}
	}
	for i, element := range metadata.Waits {
		if err := appendNamed("waits", i, element.Name); err != nil {
			return nil, err
		}
	}
	return out, nil
}
