package metadata

import (
	"reflect"
	"strings"
	"testing"

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

func TestLowerFlowComponentHeadersProjectsEquivalentFlowIdentity(t *testing.T) {
	definition := &models.FlowDefinition{
		Id:            "fd-workflow",
		DeveloperName: "FulfillmentFlow",
		MasterLabel:   "Fulfillment Flow",
		OwnerPack:     "toy-workflow-pack",
	}
	version := &models.FlowVersion{
		Id:                  "fv-workflow-v3",
		DefinitionId:        "fd-workflow",
		VersionNumber:       3,
		DefinitionOwnerPack: "toy-workflow-pack",
		Metadata: models.FlowMetadata{
			Start:       &models.FlowStart{},
			Decisions:   []models.FlowDecision{{Name: "CheckStatus"}},
			Assignments: []models.FlowAssignment{{Name: "SetStatus"}},
			ActionCalls: []models.FlowActionCall{{Name: "NotifyOwner"}},
		},
	}

	doc, err := LowerFlowComponentHeaders(definition, version, testFlowComponentIRSource())
	if err != nil {
		t.Fatalf("LowerFlowComponentHeaders() error = %v", err)
	}

	versionID := flowVersionNodeID("FulfillmentFlow", 3)
	got, err := ProjectFlowElementIdentityHeaders(doc, versionID)
	if err != nil {
		t.Fatalf("ProjectFlowElementIdentityHeaders() error = %v", err)
	}
	want := FlowElementIdentityProjection{
		VersionNodeID: "FulfillmentFlow.v3",
		ElementNames:  []string{"CheckStatus", "SetStatus", "NotifyOwner"},
	}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("projected flow identity = %#v, want %#v", got, want)
	}
	if got := len(doc.Nodes); got != 3 {
		t.Fatalf("lowered node count = %d, want 3", got)
	}
	if node := doc.Nodes[0]; node.Kind != metadatair.NodeKindFlowElement || node.ID != "FulfillmentFlow.v3.CheckStatus" || node.Source.Path != "/decisions/0" {
		t.Fatalf("decision node = %#v", node)
	}
	if node := doc.Nodes[1]; node.Kind != metadatair.NodeKindFlowElement || node.ID != "FulfillmentFlow.v3.SetStatus" || node.Source.Path != "/assignments/0" {
		t.Fatalf("assignment node = %#v", node)
	}
}

func TestLowerFlowComponentHeadersProjectsCallableStartElementReference(t *testing.T) {
	definition := &models.FlowDefinition{DeveloperName: "CallableFlow"}
	version := &models.FlowVersion{
		VersionNumber: 1,
		Metadata: models.FlowMetadata{
			StartElementReference: "Launch",
			Assignments:           []models.FlowAssignment{{Name: "Launch"}},
		},
	}

	doc, err := LowerFlowComponentHeaders(definition, version, testFlowComponentIRSource())
	if err != nil {
		t.Fatalf("LowerFlowComponentHeaders() error = %v", err)
	}

	got, err := ProjectFlowElementIdentityHeaders(doc, flowVersionNodeID("CallableFlow", 1))
	if err != nil {
		t.Fatalf("ProjectFlowElementIdentityHeaders() error = %v", err)
	}
	want := FlowElementIdentityProjection{
		VersionNodeID: "CallableFlow.v1",
		ElementNames:  []string{"Launch"},
	}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("projected callable flow identity = %#v, want %#v", got, want)
	}
	if node := doc.Nodes[0]; node.Source.Path != "/assignments/0" {
		t.Fatalf("callable assignment node source path = %q, want /assignments/0", node.Source.Path)
	}
}

func TestLowerFlowComponentHeadersSkipsScreensInExecutableElementSlice(t *testing.T) {
	definition := &models.FlowDefinition{DeveloperName: "ScreenFlow"}
	version := &models.FlowVersion{
		VersionNumber: 1,
		Metadata: models.FlowMetadata{
			Assignments: []models.FlowAssignment{{Name: "PrepareState"}},
			Screens:     []models.FlowScreen{{Name: "CollectInput"}},
		},
	}

	doc, err := LowerFlowComponentHeaders(definition, version, testFlowComponentIRSource())
	if err != nil {
		t.Fatalf("LowerFlowComponentHeaders() error = %v", err)
	}

	got, err := ProjectFlowElementIdentityHeaders(doc, flowVersionNodeID("ScreenFlow", 1))
	if err != nil {
		t.Fatalf("ProjectFlowElementIdentityHeaders() error = %v", err)
	}
	want := FlowElementIdentityProjection{
		VersionNodeID: "ScreenFlow.v1",
		ElementNames:  []string{"PrepareState"},
	}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("projected screen flow identity = %#v, want %#v", got, want)
	}
}

func TestLowerFlowComponentHeadersFailsClosedOnDuplicateElementIdentity(t *testing.T) {
	definition := &models.FlowDefinition{DeveloperName: "FulfillmentFlow"}
	version := &models.FlowVersion{
		VersionNumber: 2,
		Metadata: models.FlowMetadata{
			Decisions:   []models.FlowDecision{{Name: "SharedElement"}},
			Assignments: []models.FlowAssignment{{Name: "SharedElement"}},
		},
	}

	_, err := LowerFlowComponentHeaders(definition, version, testFlowComponentIRSource())
	if err == nil {
		t.Fatal("LowerFlowComponentHeaders() error = nil, want duplicate rejection")
	}
	if !strings.Contains(err.Error(), "duplicate node identity flow_element:FulfillmentFlow.v2.SharedElement") {
		t.Fatalf("LowerFlowComponentHeaders() error = %q, want duplicate identity", err.Error())
	}
}

func TestLowerFlowComponentHeadersRequiresPackOwnerForFlowSource(t *testing.T) {
	source := testFlowComponentIRSource()
	source.Owner = metadatair.Owner{Layer: metadatair.OwnerLayerL1}

	_, err := LowerFlowComponentHeaders(
		&models.FlowDefinition{DeveloperName: "FulfillmentFlow"},
		&models.FlowVersion{VersionNumber: 1},
		source,
	)
	if err == nil {
		t.Fatal("LowerFlowComponentHeaders() error = nil, want owner-pack rejection")
	}
	if !strings.Contains(err.Error(), "L1 owner requires an owner pack") {
		t.Fatalf("LowerFlowComponentHeaders() error = %q, want owner-pack rejection", err.Error())
	}
}

func TestLowerFlowComponentHeadersRejectsDefinitionVersionMismatch(t *testing.T) {
	_, err := LowerFlowComponentHeaders(
		&models.FlowDefinition{Id: "definition-a", DeveloperName: "FulfillmentFlow"},
		&models.FlowVersion{DefinitionId: "definition-b", VersionNumber: 1},
		testFlowComponentIRSource(),
	)
	if err == nil {
		t.Fatal("LowerFlowComponentHeaders() error = nil, want definition mismatch")
	}
	if !strings.Contains(err.Error(), "does not match definition id") {
		t.Fatalf("LowerFlowComponentHeaders() error = %q, want definition mismatch", err.Error())
	}
}

func TestProjectFlowElementIdentityHeadersRejectsForeignElementNode(t *testing.T) {
	version, err := metadatair.ParseVersion("1.0.0")
	if err != nil {
		t.Fatal(err)
	}
	source := testFlowComponentIRSource()
	doc := metadatair.Document{
		Version: version,
		Nodes: []metadatair.NodeHeader{
			flowComponentNode(source, metadatair.NodeKindFlowElement, "OtherFlow.v1.CheckStatus", "/decisions/0"),
		},
	}

	_, err = ProjectFlowElementIdentityHeaders(doc, metadatair.NodeID("FulfillmentFlow.v1"))
	if err == nil {
		t.Fatal("ProjectFlowElementIdentityHeaders() error = nil, want foreign element rejection")
	}
	if !strings.Contains(err.Error(), "does not belong to version FulfillmentFlow.v1") {
		t.Fatalf("ProjectFlowElementIdentityHeaders() error = %q, want version ownership rejection", err.Error())
	}
}

func TestProjectFlowElementIdentityHeadersRejectsDefinitionVersionNodes(t *testing.T) {
	version, err := metadatair.ParseVersion("1.0.0")
	if err != nil {
		t.Fatal(err)
	}
	source := testFlowComponentIRSource()
	doc := metadatair.Document{
		Version: version,
		Nodes: []metadatair.NodeHeader{
			flowComponentNode(source, metadatair.NodeKindFlowDefinition, "FulfillmentFlow", "/"),
			flowComponentNode(source, metadatair.NodeKindFlowElement, "OtherFlow.v1.CheckStatus", "/decisions/0"),
		},
	}

	_, err = ProjectFlowElementIdentityHeaders(doc, metadatair.NodeID("FulfillmentFlow.v1"))
	if err == nil {
		t.Fatal("ProjectFlowElementIdentityHeaders() error = nil, want non-element rejection")
	}
	if !strings.Contains(err.Error(), "requires only FlowElement nodes") {
		t.Fatalf("ProjectFlowElementIdentityHeaders() error = %q, want non-element rejection", err.Error())
	}
}

func testFlowComponentIRSource() FlowComponentIRSource {
	return FlowComponentIRSource{
		Owner: metadatair.Owner{
			Layer:     metadatair.OwnerLayerL1,
			OwnerPack: "toy-workflow-pack",
		},
		URI:       "pack://toy-workflow/flows/FulfillmentFlow.flow-meta.json",
		Component: "flows/FulfillmentFlow",
		Dialect:   "toy",
	}
}
