package catalogreconcile

import (
	"bytes"
	"context"
	"encoding/json"
	"reflect"
	"strings"
	"testing"

	"github.com/nexuscrm/kernel/pkg/seedrows"
	"github.com/nexuscrm/metadata/catalogsnapshot"
	"github.com/nexuscrm/shared/pkg/models"
	consumerv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	controlplane "github.com/pingcap-inc/nexuscrm-contract/pkg/controlplane"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/flowv1"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/queryv1"
	"github.com/pingcap-inc/nexuscrm-controlplane/componentbehavior"
)

type completeV3FlowReader struct {
	*markedReader
	reportTypes []controlplane.ReportTypeDeclaration
	layouts     map[string][]*models.PageLayout
}

func (r *completeV3FlowReader) GetEffectiveConnectedApps(context.Context) ([]EffectiveOAuthAuthorityEntry, error) {
	return nil, nil
}

func (r *completeV3FlowReader) GetEffectiveOAuthCustomScopes(context.Context) ([]EffectiveOAuthAuthorityEntry, error) {
	return nil, nil
}

func (r *completeV3FlowReader) GetLayouts(
	ctx context.Context,
	objectName string,
) ([]*models.PageLayout, error) {
	r.requireTransaction(ctx)
	return r.layouts[objectName], nil
}

func (r *completeV3FlowReader) GetAllActions(context.Context) ([]*models.ActionMetadata, error) {
	return nil, nil
}

func (r *completeV3FlowReader) GetEffectiveStandardActions(context.Context, []string) ([]seedrows.StandardActionDef, error) {
	return nil, nil
}

func (r *completeV3FlowReader) GetEffectiveReportTypes(context.Context) ([]controlplane.ReportTypeDeclaration, error) {
	return append([]controlplane.ReportTypeDeclaration(nil), r.reportTypes...), nil
}

func TestBuildEffectiveStandardFlowComponentsPublishesOnlyAuthorizedOverrides(
	t *testing.T,
) {
	base := models.FlowMetadata{
		Status: "Active", Label: "Base", RunAtomically: true,
	}
	baseBehavior, err := json.Marshal(base)
	if err != nil {
		t.Fatal(err)
	}
	release := verifiedPlatformRelease(t, []platformReleaseComponentFixture{
		{kind: appReleaseStandardFlowComponentKind, businessKey: "AppFlow", behavior: baseBehavior},
	})
	override := models.FlowMetadata{
		Status: "Active", Label: "Tenant override", RunAtomically: true,
	}
	components, err := buildEffectiveStandardFlowComponents(
		&release,
		[]effectiveActiveFlow{
			{
				definition: &models.FlowDefinition{DeveloperName: "AppFlow"},
				version: &models.FlowVersion{
					VersionNumber: 2,
					Metadata:      override,
				},
			},
			{
				definition: &models.FlowDefinition{DeveloperName: "TenantOnlyFlow"},
				version: &models.FlowVersion{
					VersionNumber: 1,
					Metadata:      models.FlowMetadata{Status: "Active"},
				},
			},
		},
	)
	if err != nil {
		t.Fatal(err)
	}
	if len(components) != 1 ||
		components[0].ComponentKind != consumerv1.EffectiveStandardFlowComponentKind ||
		components[0].BusinessKey != "AppFlow" {
		t.Fatalf("effective components = %#v", components)
	}
	document, err := consumerv1.DecodeEffectiveStandardFlowDocument(
		components[0].CanonicalBehavior,
	)
	if err != nil {
		t.Fatal(err)
	}
	overrideBehavior, err := json.Marshal(override)
	if err != nil {
		t.Fatal(err)
	}
	overrideBehavior, err = componentbehavior.CanonicalJSONV1(overrideBehavior)
	if err != nil {
		t.Fatal(err)
	}
	if document.DefinitionVersion != "2" ||
		!bytes.Equal(document.CanonicalBehavior, overrideBehavior) {
		t.Fatalf("effective standard Flow = %#v", document)
	}
}

func TestBuildEffectiveStandardFlowComponentsOmitsUnchangedAppFlow(t *testing.T) {
	base := models.FlowMetadata{Status: "Active", Label: "Base", RunAtomically: true}
	behavior, err := json.Marshal(base)
	if err != nil {
		t.Fatal(err)
	}
	release := verifiedPlatformRelease(t, []platformReleaseComponentFixture{
		{kind: appReleaseStandardFlowComponentKind, businessKey: "AppFlow", behavior: behavior},
	})
	components, err := buildEffectiveStandardFlowComponents(
		&release,
		[]effectiveActiveFlow{{
			definition: &models.FlowDefinition{DeveloperName: "AppFlow"},
			version:    &models.FlowVersion{VersionNumber: 1, Metadata: base},
		}},
	)
	if err != nil {
		t.Fatal(err)
	}
	if len(components) != 0 {
		t.Fatalf("unchanged App Flow produced effective override: %#v", components)
	}
}

func TestBuildEffectiveStandardFlowComponentsPreservesAuthoritativeRawBehavior(
	t *testing.T,
) {
	base := []byte(`{"status":"Active","label":"Base","processType":"AutoLaunchedFlow","futureField":{"enabled":true}}`)
	override := []byte(`{"status":"Active","label":"Tenant override","processType":"AutoLaunchedFlow","futureField":{"enabled":false}}`)
	release := verifiedPlatformRelease(t, []platformReleaseComponentFixture{{
		kind: appReleaseStandardFlowComponentKind, businessKey: "AppFlow", behavior: base,
	}})
	components, err := buildEffectiveStandardFlowComponents(
		&release,
		[]effectiveActiveFlow{{
			definition: &models.FlowDefinition{DeveloperName: "AppFlow"},
			version: &models.FlowVersion{
				VersionNumber: 2,
				MetadataRaw:   override,
				MetadataJSON:  `{"status":"Active","label":"stale JSON"}`,
				Metadata:      models.FlowMetadata{Status: "Active", Label: "stale typed"},
			},
		}},
	)
	if err != nil {
		t.Fatal(err)
	}
	if len(components) != 1 {
		t.Fatalf("effective components = %#v", components)
	}
	document, err := consumerv1.DecodeEffectiveStandardFlowDocument(
		components[0].CanonicalBehavior,
	)
	if err != nil {
		t.Fatal(err)
	}
	want, err := componentbehavior.CanonicalJSONV1(override)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(document.CanonicalBehavior, want) {
		t.Fatalf("effective behavior = %s, want %s", document.CanonicalBehavior, want)
	}
}

func TestBuildEffectiveStandardFlowComponentsUsesStoredJSONBeforeTypedProjection(
	t *testing.T,
) {
	base := []byte(`{"label":"Base","status":"Active"}`)
	override := []byte(`{"label":"Stored override","processType":"AutoLaunchedFlow","status":"Active"}`)
	release := verifiedPlatformRelease(t, []platformReleaseComponentFixture{{
		kind: appReleaseStandardFlowComponentKind, businessKey: "AppFlow", behavior: base,
	}})
	components, err := buildEffectiveStandardFlowComponents(
		&release,
		[]effectiveActiveFlow{{
			definition: &models.FlowDefinition{DeveloperName: "AppFlow"},
			version: &models.FlowVersion{
				VersionNumber: 2,
				MetadataJSON:  string(override),
				Metadata:      models.FlowMetadata{Status: "Active", Label: "stale typed"},
			},
		}},
	)
	if err != nil {
		t.Fatal(err)
	}
	document, err := consumerv1.DecodeEffectiveStandardFlowDocument(
		components[0].CanonicalBehavior,
	)
	if err != nil {
		t.Fatal(err)
	}
	want, err := componentbehavior.CanonicalJSONV1(override)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(document.CanonicalBehavior, want) {
		t.Fatalf("effective behavior = %s, want %s", document.CanonicalBehavior, want)
	}
}

func TestReadEffectiveActiveFlowsJoinsDefinitionOwnershipIntoVersion(t *testing.T) {
	storedVersion := &models.FlowVersion{
		Id: "version-companion", DefinitionId: "definition-companion", VersionNumber: 3,
		Status: models.FlowStatusActive, Source: "standard",
	}
	reader := &markedReader{
		t: t,
		flowDefinitions: []*models.FlowDefinition{{
			Id: "definition-companion", DeveloperName: "CompanionFlow",
			ActiveVersionId: "version-companion", OwnerPack: "  companion-pack  ",
			IsCustomized: true,
		}},
		flowVersions: map[string][]*models.FlowVersion{
			"definition-companion": {storedVersion},
		},
	}

	ctx := context.WithValue(context.Background(), transactionMarker{}, "one-snapshot")
	flows, err := readEffectiveActiveFlows(ctx, reader)
	if err != nil {
		t.Fatal(err)
	}
	if len(flows) != 1 || flows[0].version == nil {
		t.Fatalf("effective active Flows = %#v", flows)
	}
	if flows[0].version.DefinitionOwnerPack != "companion-pack" ||
		!flows[0].version.DefinitionIsCustomized {
		t.Fatalf("joined FlowVersion ownership = %#v", flows[0].version)
	}
	if storedVersion.DefinitionOwnerPack != "" || storedVersion.DefinitionIsCustomized {
		t.Fatal("effective assembly mutated the repository-owned FlowVersion")
	}
}

func TestReadEffectiveMetadataSealsOneFlowForRuntimeAndAppMetadata(t *testing.T) {
	base := []byte(`{
		"status":"Active","runAtomically":true,"startElementReference":"GetLead",
		"recordLookups":[{"name":"GetLead","object":"Lead","outputReference":"leads","getFirstRecordOnly":false,"connector":{"targetReference":"EachLead"}}],
		"loops":[{"name":"EachLead","collectionReference":"leads","assignNextValueToReference":"lead","nextValueConnector":{"targetReference":"CreateAccount"}}],
		"recordCreates":[{"name":"CreateAccount","object":"Account","fieldMapping":{"sourceObject":"Lead","sourceReference":"lead","targetObject":"Account","actionType":"convert"}}]
	}`)
	release := verifiedPlatformRelease(t, []platformReleaseComponentFixture{{
		kind: appReleaseStandardFlowComponentKind, businessKey: "AppFlow", behavior: base,
	}})
	lead, account := validObject("Lead"), validObject("Account")
	lead.Fields = append(lead.Fields, models.FieldMetadata{
		ID: "field-lead-company", APIName: "Company", Label: "Company", Type: "string", PhysicalName: "company",
	})
	account.Fields = append(account.Fields, models.FieldMetadata{
		ID: "field-account-name", APIName: "Name", Label: "Name", Type: "string", PhysicalName: "name",
	})
	reader := &completeV3FlowReader{markedReader: &markedReader{
		t: t, objects: []*models.ObjectMetadata{lead, account},
		recordTypes: map[string][]*models.RecordType{
			"Lead": {{
				ID: "rt-app", ObjectAPIName: "Lead", Name: "Prospect",
				DeveloperName: "Prospect", IsActive: true,
			}},
			"Account": {{
				ID: "rt-tenant", ObjectAPIName: "Account", Name: "Tenant Extension",
				DeveloperName: "Tenant_Extension", IsActive: true,
			}},
		},
		flowDefinitions: []*models.FlowDefinition{{
			Id: "definition-app", DeveloperName: "AppFlow", ProcessType: "AutoLaunchedFlow",
			ActiveVersionId: "version-app",
		}},
		flowVersions: map[string][]*models.FlowVersion{
			"definition-app": {{
				Id: "version-app", DefinitionId: "definition-app", VersionNumber: 1,
				Status: models.FlowStatusActive, ProcessType: "AutoLaunchedFlow", MetadataRaw: base,
			}},
		},
		fieldMappings: []catalogsnapshot.FieldMappingRule{{
			SourceObject: "Lead", TargetObject: "Account", ActionType: "convert",
			SourceField: "Company", TargetField: "Name",
		}},
	}, reportTypes: []controlplane.ReportTypeDeclaration{{
		FullName: "OpportunityHistory", Label: "Opportunity History",
		BaseObject: "Opportunity", Deployed: true,
		Metadata: json.RawMessage(`{"virtualSource":{"sourceRef":"OpportunityHistory"}}`),
	}}}
	selection := MetadataSelection{
		OrgID: "org-test", AppInstallationID: "installation-test",
		RuntimeCapabilityIDs: []string{flowv1.CapabilityName},
		CatalogRevision:      7, SchemaGeneration: 8, EngineGeneration: 9,
	}
	result, err := readEffectiveMetadata(
		context.Background(),
		func(ctx context.Context, fn func(context.Context) error) error {
			return fn(context.WithValue(ctx, transactionMarker{}, "one-snapshot"))
		},
		reader, selection, &release, consumerv1.AppMetadataV3CapabilityName,
	)
	if err != nil {
		t.Fatal(err)
	}
	if reader.fieldMappingCalls != 1 || reader.recordTypeCalls != 2 {
		t.Fatalf(
			"same-transaction reads = fieldMappings:%d recordTypes:%d, want 1/2",
			reader.fieldMappingCalls, reader.recordTypeCalls,
		)
	}
	artifact, err := catalogsnapshot.CompileEffectiveMetadata(result.metadata)
	if err != nil {
		t.Fatal(err)
	}
	snapshot, err := catalogsnapshot.OpenCatalogSnapshot(artifact)
	if err != nil {
		t.Fatal(err)
	}
	viewArtifact, err := catalogsnapshot.ProjectRuntimeMetadataView(snapshot, catalogsnapshot.ProjectionKey{
		AppInstallationID: selection.AppInstallationID,
		CapabilityID:      flowv1.CapabilityName,
	})
	if err != nil {
		t.Fatal(err)
	}
	view, err := catalogsnapshot.OpenRuntimeMetadataView(viewArtifact)
	if err != nil {
		t.Fatal(err)
	}
	runtimeFlow, ok := view.ActiveFlowVersion("AppFlow")
	if !ok || runtimeFlow == nil {
		t.Fatal("Runtime view omitted AppFlow")
	}

	var published []byte
	for _, component := range result.components {
		if component.ComponentKind != consumerv1.EffectiveStandardFlowComponentKind || component.BusinessKey != "AppFlow" {
			continue
		}
		document, err := consumerv1.DecodeEffectiveStandardFlowDocument(component.CanonicalBehavior)
		if err != nil {
			t.Fatal(err)
		}
		published = document.CanonicalBehavior
	}
	if len(published) == 0 {
		t.Fatal("derived Flow did not publish an effective AppMetadata component")
	}
	reportTypePublished := false
	for _, component := range result.components {
		if component.ComponentKind == consumerv1.EffectiveReportTypeComponentKind &&
			component.BusinessKey == "OpportunityHistory" {
			reportTypePublished = true
		}
	}
	if !reportTypePublished {
		t.Fatal("effective L2 report type did not publish into the same AppMetadata V3 selection")
	}
	recordTypePublished := false
	for _, component := range result.components {
		if component.ComponentKind != consumerv1.EffectiveRecordTypeComponentKind ||
			component.BusinessKey != "Account" {
			continue
		}
		var document effectiveRecordTypeDocument
		if err := json.Unmarshal(component.CanonicalBehavior, &document); err != nil {
			t.Fatal(err)
		}
		recordTypePublished = len(document.RecordTypes) == 1 &&
			document.RecordTypes[0].RecordTypeID == "rt-tenant" &&
			document.RecordTypes[0].Name == "Tenant Extension"
	}
	if !recordTypePublished {
		t.Fatal("effective L2 RecordType did not publish from the same AppMetadata V3 transaction")
	}
	runtimeBehavior, err := componentbehavior.CanonicalJSONV1(runtimeFlow.MetadataRaw)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(runtimeBehavior, published) {
		t.Fatalf("Runtime Flow and AppMetadata Flow differ:\nruntime=%s\napp=%s", runtimeBehavior, published)
	}
	var metadata struct {
		RecordLookups []struct {
			QueriedFields []string `json:"queriedFields"`
		} `json:"recordLookups"`
	}
	if err := json.Unmarshal(runtimeBehavior, &metadata); err != nil {
		t.Fatal(err)
	}
	if got, want := metadata.RecordLookups[0].QueriedFields, []string{"Company", "Id"}; !reflect.DeepEqual(got, want) {
		t.Fatalf("derived queriedFields = %v, want %v", got, want)
	}
}

func TestBuildEffectiveStandardFlowComponentsCanonicalizesComparisonAndFailsClosed(
	t *testing.T,
) {
	base := []byte("{\n  \"status\": \"Active\", \"label\": \"Base\"\n}")
	release := verifiedPlatformRelease(t, []platformReleaseComponentFixture{{
		kind: appReleaseStandardFlowComponentKind, businessKey: "AppFlow", behavior: base,
	}})
	components, err := buildEffectiveStandardFlowComponents(
		&release,
		[]effectiveActiveFlow{{
			definition: &models.FlowDefinition{DeveloperName: "AppFlow"},
			version: &models.FlowVersion{
				VersionNumber: 1,
				MetadataRaw:   []byte(`{"label":"Base","status":"Active"}`),
			},
		}},
	)
	if err != nil {
		t.Fatal(err)
	}
	if len(components) != 0 {
		t.Fatalf("formatting-only difference produced override: %#v", components)
	}

	_, err = buildEffectiveStandardFlowComponents(
		&release,
		[]effectiveActiveFlow{{
			definition: &models.FlowDefinition{DeveloperName: "AppFlow"},
			version: &models.FlowVersion{
				VersionNumber: 2,
				MetadataRaw:   []byte(`{"status":"Active","status":"Draft"}`),
			},
		}},
	)
	if err == nil || !strings.Contains(err.Error(), "duplicate") {
		t.Fatalf("duplicate-key behavior error = %v", err)
	}
}

func TestAppMetadataV2ReadsEffectiveFlowWithoutMutationSelection(t *testing.T) {
	base := models.FlowMetadata{Status: "Active", Label: "Base", RunAtomically: true}
	baseBehavior, err := json.Marshal(base)
	if err != nil {
		t.Fatal(err)
	}
	release := verifiedPlatformRelease(t, []platformReleaseComponentFixture{{
		kind: appReleaseStandardFlowComponentKind, businessKey: "AppFlow", behavior: baseBehavior,
	}})
	reader := &markedReader{
		t:       t,
		objects: []*models.ObjectMetadata{validObject("WorkItem")},
		flowDefinitions: []*models.FlowDefinition{{
			Id: "flow-definition", DeveloperName: "AppFlow", ActiveVersionId: "flow-version-2",
		}},
		flowVersions: map[string][]*models.FlowVersion{
			"flow-definition": {{
				Id: "flow-version-2", DefinitionId: "flow-definition", VersionNumber: 2,
				Status: models.FlowStatusActive,
				Metadata: models.FlowMetadata{
					Status: "Active", Label: "Effective override", RunAtomically: true,
				},
			}},
		},
	}
	selection := MetadataSelection{
		OrgID: "org-test", AppInstallationID: "installation-test",
		RuntimeCapabilityID: queryv1.CapabilityName,
		CatalogRevision:     7, SchemaGeneration: 8, EngineGeneration: 9,
	}
	result, err := readEffectiveMetadata(
		context.Background(),
		func(ctx context.Context, fn func(context.Context) error) error {
			return fn(context.WithValue(ctx, transactionMarker{}, "one-snapshot"))
		},
		reader, selection, &release, consumerv1.AppMetadataV2CapabilityName,
	)
	if err != nil {
		t.Fatal(err)
	}
	if reader.flowReadCalls != 1 {
		t.Fatalf("effective Flow reads = %d, want 1", reader.flowReadCalls)
	}
	found := false
	for _, component := range result.components {
		if component.ComponentKind == consumerv1.EffectiveStandardFlowComponentKind &&
			component.BusinessKey == "AppFlow" {
			found = true
			break
		}
	}
	if !found {
		t.Fatalf("AppMetadataView V2 omitted effective Flow without mutation selection: %#v", result.components)
	}
}
