package packs

import (
	"context"
	"database/sql"
	"reflect"
	"strings"
	"testing"

	cp "github.com/pingcap-inc/nexuscrm-contract/pkg/controlplane"
)

func TestComponentKindRegistrationSetBindsManagedLifecycleInOneSnapshot(t *testing.T) {
	legacy := testComponentKindRegistration(cp.ComponentKindApprovalProcess, "ProcessDefinition")
	lifecycle := testComponentManagedLifecycleRegistration(t, cp.ComponentKindApprovalProcess, "ProcessDefinition")
	bound, err := legacy.WithManagedLifecycle(lifecycle)
	if err != nil {
		t.Fatal(err)
	}
	presentation := cp.ComponentKindRegistration{
		Contract:     cp.ComponentKindContract{Kind: cp.ComponentKindSearchScopeOrder},
		Capability:   cp.SourceBackedComponentCapability{Kind: cp.ComponentKindSearchScopeOrder, Installable: true},
		BuildHandler: func(cp.ComponentBuildContext, cp.ComponentBuildRequest) error { return nil },
	}

	set, err := NewComponentKindRegistrationSetWithManagedLifecycles(
		[]cp.ComponentKindRegistration{presentation},
		[]cp.ComponentKindManagedLifecycleRegistration{bound},
	)
	if err != nil {
		t.Fatal(err)
	}
	registrations := set.Registrations()
	if len(registrations) != 2 || registrations[0].Contract.Kind != cp.ComponentKindApprovalProcess || registrations[1].Contract.Kind != cp.ComponentKindSearchScopeOrder {
		t.Fatalf("registrations = %#v", registrations)
	}
	registeredLifecycle, ok := (&set).managedLifecycle(ComponentKindApprovalProcess)
	if !ok || registeredLifecycle.Kind() != cp.ComponentKindApprovalProcess {
		t.Fatalf("managed lifecycle = %#v, %v", registeredLifecycle, ok)
	}
	if _, ok := (&set).managedRegistration(ComponentKindApprovalProcess); ok {
		t.Fatal("lifecycle registration also exposed the legacy provider path")
	}
	if registrations[0].ManagedProvider != nil {
		t.Fatal("lifecycle snapshot retained the legacy provider path")
	}
	if legacy.ManagedProvider == nil {
		t.Fatal("lifecycle binding mutated the caller's legacy registration")
	}
	if set.HasManagedOwnerPackTable(ComponentKindApprovalProcess, "ProcessDefinition") {
		t.Fatal("item-only lifecycle acquired complete OwnerPack table coverage")
	}
	exactSetLifecycle, err := cp.NewComponentManagedLifecycleRegistration(cp.ComponentManagedLifecycleRegistrationSpec{
		Kind: lifecycle.Kind(), ReadSet: lifecycle.ReadSet(),
		Capabilities: append(lifecycle.Capabilities(), cp.ComponentManagedCapabilityExactSet), EffectClasses: lifecycle.EffectClasses(),
		TransactionMode: lifecycle.TransactionMode(), Budget: lifecycle.Budget(), Lifecycle: lifecycle.Lifecycle(),
	})
	if err != nil {
		t.Fatal(err)
	}
	exactSetBound, err := legacy.WithManagedLifecycle(exactSetLifecycle)
	if err != nil {
		t.Fatal(err)
	}
	exactSet, err := NewComponentKindRegistrationSetWithManagedLifecycles(nil, []cp.ComponentKindManagedLifecycleRegistration{exactSetBound})
	if err != nil {
		t.Fatal(err)
	}
	if !exactSet.HasManagedOwnerPackTable(ComponentKindApprovalProcess, "ProcessDefinition") {
		t.Fatal("exact-set lifecycle did not retain complete OwnerPack table coverage")
	}

	copyOfSet := set
	rebuilt, err := NewComponentKindRegistrationSetWithManagedLifecycles(nil, set.ManagedLifecycles())
	if err != nil {
		t.Fatal(err)
	}
	if !set.SameSnapshot(copyOfSet) || set.SameSnapshot(rebuilt) {
		t.Fatal("managed lifecycle snapshot identity is not exact")
	}
}

func TestApplyDepsPublicShapeRemainsSourceCompatible(t *testing.T) {
	depsType := reflect.TypeOf(ApplyDeps{})
	wantFields := []string{
		"DB", "DBConn", "TxManager", "Metadata", "Schema", "Permissions", "System", "FormulaView",
		"AutoNumberMetadata", "MetadataRepo", "SessionRepo", "ConnectedAppHook", "Options",
	}
	if depsType.NumField() != len(wantFields) {
		t.Fatalf("ApplyDeps field count = %d, want %d", depsType.NumField(), len(wantFields))
	}
	for index, want := range wantFields {
		if got := depsType.Field(index).Name; got != want {
			t.Fatalf("ApplyDeps field %d = %q, want %q", index, got, want)
		}
	}
}

func TestComponentKindRegistrationSetRejectsDuplicateKindAcrossLifecyclePaths(t *testing.T) {
	legacy := testComponentKindRegistration(cp.ComponentKindApprovalProcess, "ProcessDefinition")
	lifecycle := testComponentManagedLifecycleRegistration(t, cp.ComponentKindApprovalProcess, "ProcessDefinition")
	base := cloneComponentKindRegistration(legacy)
	base.ManagedProvider = nil
	bound, err := base.WithManagedLifecycle(lifecycle)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := NewComponentKindRegistrationSetWithManagedLifecycles([]cp.ComponentKindRegistration{legacy}, []cp.ComponentKindManagedLifecycleRegistration{bound}); err == nil || !strings.Contains(err.Error(), "registered more than once") {
		t.Fatalf("duplicate-kind error = %v", err)
	}
}

func TestComponentKindRegistrationSetProductionGateRejectsOnlyActiveLegacyProviders(t *testing.T) {
	legacy := testComponentKindRegistration(cp.ComponentKindApprovalProcess, "ProcessDefinition")
	legacySet, err := NewComponentKindRegistrationSet([]cp.ComponentKindRegistration{legacy})
	if err != nil {
		t.Fatal(err)
	}
	if err := legacySet.ValidateNoActiveLegacyManagedProviders(); err == nil || !strings.Contains(err.Error(), string(cp.ComponentKindApprovalProcess)) {
		t.Fatalf("legacy production-gate error = %v", err)
	}
	base := cloneComponentKindRegistration(legacy)
	base.ManagedProvider = nil
	lifecycle := testComponentManagedLifecycleRegistration(t, cp.ComponentKindApprovalProcess, "ProcessDefinition")
	bound, err := base.WithManagedLifecycle(lifecycle)
	if err != nil {
		t.Fatal(err)
	}
	lifecycleSet, err := NewComponentKindRegistrationSetWithManagedLifecycles(nil, []cp.ComponentKindManagedLifecycleRegistration{bound})
	if err != nil {
		t.Fatal(err)
	}
	if err := lifecycleSet.ValidateNoActiveLegacyManagedProviders(); err != nil {
		t.Fatalf("lifecycle production gate: %v", err)
	}
	if _, err := legacySet.WithManagedLifecycleRuntime(nil, true); err == nil || !strings.Contains(err.Error(), string(cp.ComponentKindApprovalProcess)) {
		t.Fatalf("configured legacy production-gate error = %v", err)
	}
	class := cp.ComponentManagedEffectClass("revision")
	configured, err := lifecycleSet.WithManagedLifecycleRuntime([]ManagedLifecycleEffectAdapter{{
		Class:      class,
		LockPolicy: ManagedLifecycleEffectManagedStateOnly,
		Apply: func(context.Context, *sql.Tx, ManagedLifecycleEffectRequest) ([]byte, error) {
			return []byte("receipt"), nil
		},
		Prove: func(context.Context, *sql.DB, ManagedLifecycleEffectRequest, []byte) (bool, error) { return true, nil },
	}}, true)
	if err != nil {
		t.Fatal(err)
	}
	if err := configured.ValidateManagedLifecycleRuntime(); !configured.ManagedLifecycleRuntimeConfigured() || err != nil {
		t.Fatalf("configured lifecycle runtime = %v, %v", configured.ManagedLifecycleRuntimeConfigured(), err)
	}
	effects := configured.ManagedLifecycleEffectAdapters()
	if len(effects) != 1 || effects[0].Class != class {
		t.Fatalf("configured effects = %#v", effects)
	}
	effects[0].Class = "mutated"
	if configured.ManagedLifecycleEffectAdapters()[0].Class != class {
		t.Fatal("managed lifecycle effect snapshot changed through caller mutation")
	}
	copyOfConfigured := configured
	cloned, err := configured.Clone()
	if err != nil {
		t.Fatal(err)
	}
	if err := cloned.ValidateManagedLifecycleRuntime(); !configured.SameSnapshot(copyOfConfigured) || configured.SameSnapshot(cloned) || err != nil {
		t.Fatalf("managed lifecycle runtime clone identity/validation = %v/%v/%v", configured.SameSnapshot(copyOfConfigured), configured.SameSnapshot(cloned), err)
	}
}

func TestComponentKindRegistrationSetOwnsImmutableCanonicalSnapshot(t *testing.T) {
	registrations := []cp.ComponentKindRegistration{
		testComponentKindRegistration(cp.ComponentKindUIAction, "StandardAction"),
		testComponentKindRegistration(cp.ComponentKindApprovalProcess, "ProcessDefinition"),
	}
	set, err := NewComponentKindRegistrationSet(registrations)
	if err != nil {
		t.Fatal(err)
	}
	got := set.Registrations()
	if len(got) != 2 || got[0].Contract.Kind != cp.ComponentKindApprovalProcess || got[1].Contract.Kind != cp.ComponentKindUIAction {
		t.Fatalf("registrations = %#v", got)
	}
	if !set.HasManagedOwnerPackTable(ComponentKindApprovalProcess, "ProcessDefinition") || set.HasManagedOwnerPackTable(ComponentKindStandardFlow, "FlowDefinition") {
		t.Fatalf("managed kind membership is inconsistent with registrations: %#v", got)
	}
	presentationOnly := testComponentKindRegistration(cp.ComponentKindApprovalProcess, "ProcessDefinition")
	presentationOnly.ManagedProvider = nil
	presentationSet, err := NewComponentKindRegistrationSet([]cp.ComponentKindRegistration{presentationOnly})
	if err != nil {
		t.Fatalf("presentation-only registration set: %v", err)
	}
	if presentationSet.HasManagedOwnerPackTable(ComponentKindApprovalProcess, "ProcessDefinition") {
		t.Fatal("presentation-only approval_process registration acquired managed ownership")
	}
	sourceOwned := testComponentKindRegistration(cp.ComponentKindApprovalProcess, "ProcessDefinition")
	sourceOwned.Contract.OwnerDiscriminator = cp.ComponentOwnerStrategySource
	sourceOwnedSet, err := NewComponentKindRegistrationSet([]cp.ComponentKindRegistration{sourceOwned})
	if err != nil {
		t.Fatalf("source-owned registration set: %v", err)
	}
	if sourceOwnedSet.HasManagedOwnerPackTable(ComponentKindApprovalProcess, "ProcessDefinition") {
		t.Fatal("source-owned approval_process registration acquired OwnerPack coverage")
	}
	wrongSurface := testComponentKindRegistration(cp.ComponentKindApprovalProcess, "ProcessNode")
	wrongSurfaceSet, err := NewComponentKindRegistrationSet([]cp.ComponentKindRegistration{wrongSurface})
	if err != nil {
		t.Fatalf("wrong-surface registration set: %v", err)
	}
	if wrongSurfaceSet.HasManagedOwnerPackTable(ComponentKindApprovalProcess, "ProcessDefinition") {
		t.Fatal("approval_process registration without ProcessDefinition acquired coverage")
	}
	caseDriftedSurface := testComponentKindRegistration(cp.ComponentKindApprovalProcess, "processdefinition")
	caseDriftedSurfaceSet, err := NewComponentKindRegistrationSet([]cp.ComponentKindRegistration{caseDriftedSurface})
	if err != nil {
		t.Fatalf("case-drifted surface registration set: %v", err)
	}
	if caseDriftedSurfaceSet.HasManagedOwnerPackTable(ComponentKindApprovalProcess, "ProcessDefinition") {
		t.Fatal("case-drifted ProcessDefinition surface acquired exact coverage")
	}

	registrations[1].Contract.WriteSet[0].Table = "MutatedInput"
	got[0].Contract.WriteSet[0].Table = "MutatedOutput"
	if table := set.Registrations()[0].Contract.WriteSet[0].Table; table != "ProcessDefinition" {
		t.Fatalf("snapshot changed through caller mutation: %q", table)
	}
	copyOfSet := set
	rebuilt, err := NewComponentKindRegistrationSet(set.Registrations())
	if err != nil {
		t.Fatal(err)
	}
	if !set.SameSnapshot(copyOfSet) || set.SameSnapshot(rebuilt) ||
		!(ComponentKindRegistrationSet{}).SameSnapshot(ComponentKindRegistrationSet{}) {
		t.Fatal("registration snapshot identity is not exact")
	}
}

func TestComponentRegistrationSetRejectsCaseInsensitiveCrossKindTableSharing(t *testing.T) {
	registrations := []cp.ComponentKindRegistration{
		testComponentKindRegistration(cp.ComponentKindApprovalProcess, "ProcessDefinition"),
		testComponentKindRegistration(cp.ComponentKindUIAction, "processdefinition"),
	}
	if _, err := NewComponentKindRegistrationSet(registrations); err == nil || !strings.Contains(err.Error(), "both claim write table") {
		t.Fatalf("cross-kind table error = %v", err)
	}
	duplicateKind := []cp.ComponentKindRegistration{
		testComponentKindRegistration(cp.ComponentKindApprovalProcess, "ProcessDefinition"),
		testComponentKindRegistration(cp.ComponentKindApprovalProcess, "OtherTable"),
	}
	if _, err := NewComponentKindRegistrationSet(duplicateKind); err == nil || !strings.Contains(err.Error(), "registered more than once") {
		t.Fatalf("duplicate-kind error = %v", err)
	}
}

func TestApprovalProcessRequiresRegisteredManagedRoute(t *testing.T) {
	root := writePackFixture(t, "approval-process-pack", validPackManifest(`[
    {
      "kind": "approval_process",
      "businessKey": "Quote/Quote_Approval",
      "manageability": "locked",
      "path": "approvalProcesses/Quote_Approval.approvalProcess-meta.xml"
    }
  ]`, `"dependencies": [],`))
	writeFile(t, root, "approvalProcesses/Quote_Approval.approvalProcess-meta.xml", "<ApprovalProcess/>")
	opts := testPackSourceOptions(t)
	sourceReport := ValidatePackSourceWithOptions("local_path:"+root, opts)
	if sourceReport.HasErrors() {
		t.Fatalf("source validation failed: %s", sourceReport.Error())
	}
	if _, ok := ComponentKindContractFor(ComponentKindApprovalProcess); ok {
		t.Fatal("approval_process still has a built-in install contract")
	}
	unregistered := ValidateInstallablePackSourceWithOptions("local_path:"+root, opts)
	if !unregistered.HasErrors() || !strings.Contains(unregistered.Error(), `component kind "approval_process" is not installable`) {
		t.Fatalf("unregistered install report = %q, want managed registration requirement", unregistered.Error())
	}
	registration := testComponentKindRegistration(cp.ComponentKindApprovalProcess, "ProcessDefinition")
	if _, err := NewSourceBackedPack(sourceReport.Source, WithComponentKindRegistrations([]cp.ComponentKindRegistration{registration})); err == nil || !strings.Contains(err.Error(), "added 0 managed descriptors") {
		t.Fatalf("direct constructor error = %v", err)
	}
	if _, err := NewSourceBackedPack(sourceReport.Source,
		WithComponentKindRegistrations([]cp.ComponentKindRegistration{registration}),
		func(pack *SourceBackedPack) {
			pack.externalBuildRoutes = map[ComponentKind]externalBuildRoute{ComponentKindApprovalProcess: {}}
		},
	); err == nil || !strings.Contains(err.Error(), "more than one external build route") {
		t.Fatalf("nil route constructor error = %v", err)
	}
	if _, err := ResolveInstalledSourceBackedPackForInstalledRowAtSourceRevisionWithPackSourceOptions(
		"approval-process-pack", AppPack, SupportedManifestFormatVersion,
		PackSourceKindLocalPath, root, "", opts,
	); err == nil || !strings.Contains(err.Error(), `component kind "approval_process" is not installable`) {
		t.Fatalf("unregistered installed constructor error = %v", err)
	}
	registration.BuildHandler = func(buildContext cp.ComponentBuildContext, _ cp.ComponentBuildRequest) error {
		return buildContext.AddManagedComponent(cp.ComponentBuildManagedComponent{ExpectedProjection: []byte(`{}`)})
	}
	pack, err := NewSourceBackedPack(sourceReport.Source, WithComponentKindRegistrations([]cp.ComponentKindRegistration{registration}))
	if err != nil {
		t.Fatalf("registered constructor: %v", err)
	}
	if components := pack.Components(); len(components) != 1 {
		t.Fatalf("registered components = %d, want one managed component", len(components))
	} else if _, ok := components[0].(*sourceBackedExternalManagedComponent); !ok {
		t.Fatalf("registered component type = %T, want managed provider component", components[0])
	}

	base := cloneComponentKindRegistration(registration)
	base.ManagedProvider = nil
	lifecycle := testComponentManagedLifecycleRegistration(t, cp.ComponentKindApprovalProcess, "ProcessDefinition")
	bound, err := base.WithManagedLifecycle(lifecycle)
	if err != nil {
		t.Fatal(err)
	}
	lifecycleSet, err := NewComponentKindRegistrationSetWithManagedLifecycles(nil, []cp.ComponentKindManagedLifecycleRegistration{bound})
	if err != nil {
		t.Fatal(err)
	}
	lifecycleSet, err = lifecycleSet.WithManagedLifecycleRuntime([]ManagedLifecycleEffectAdapter{{
		Class:      "revision",
		LockPolicy: ManagedLifecycleEffectManagedStateOnly,
		Apply: func(context.Context, *sql.Tx, ManagedLifecycleEffectRequest) ([]byte, error) {
			return []byte("receipt"), nil
		},
		Prove: func(context.Context, *sql.DB, ManagedLifecycleEffectRequest, []byte) (bool, error) { return true, nil },
	}}, false)
	if err != nil {
		t.Fatal(err)
	}
	lifecyclePack, err := NewSourceBackedPack(sourceReport.Source, WithComponentKindRegistrationSet(lifecycleSet))
	if err != nil {
		t.Fatalf("lifecycle registered constructor: %v", err)
	}
	components := lifecyclePack.Components()
	if len(components) != 1 {
		t.Fatalf("lifecycle registered components = %d, want one managed component", len(components))
	}
	managed, ok := components[0].(*sourceBackedExternalManagedComponent)
	if !ok || managed.lifecycle == nil || managed.registration.ManagedProvider != nil || len(managed.effects) != 1 || managed.effects[0].Class != "revision" {
		t.Fatalf("lifecycle registered component = %#v", components[0])
	}
	managedSets, err := lifecyclePack.externalManagedSetComponents()
	if err != nil {
		t.Fatal(err)
	}
	if len(managedSets) != 0 {
		t.Fatalf("item-only lifecycle synthesized %d managed set components", len(managedSets))
	}
	exactSetLifecycle, err := cp.NewComponentManagedLifecycleRegistration(cp.ComponentManagedLifecycleRegistrationSpec{
		Kind: lifecycle.Kind(), ReadSet: lifecycle.ReadSet(),
		Capabilities: append(lifecycle.Capabilities(), cp.ComponentManagedCapabilityExactSet), EffectClasses: lifecycle.EffectClasses(),
		TransactionMode: lifecycle.TransactionMode(), Budget: lifecycle.Budget(), Lifecycle: lifecycle.Lifecycle(),
	})
	if err != nil {
		t.Fatal(err)
	}
	exactSetBound, err := base.WithManagedLifecycle(exactSetLifecycle)
	if err != nil {
		t.Fatal(err)
	}
	exactSetPack, err := NewSourceBackedPack(sourceReport.Source, WithComponentKindManagedLifecycles(nil, []cp.ComponentKindManagedLifecycleRegistration{exactSetBound}))
	if err != nil {
		t.Fatal(err)
	}
	managedSets, err = exactSetPack.externalManagedSetComponents()
	if err != nil {
		t.Fatal(err)
	}
	if len(managedSets) != 1 {
		t.Fatalf("exact-set lifecycle synthesized %d managed set components, want 1", len(managedSets))
	}
	undeclaredExactSetPack, err := NewSourceBackedPack(
		&ResolvedPackSource{Manifest: &PackManifest{Name: "empty-app-pack", Category: AppPack}},
		WithComponentKindManagedLifecycles(nil, []cp.ComponentKindManagedLifecycleRegistration{exactSetBound}),
	)
	if err != nil {
		t.Fatal(err)
	}
	managedSets, err = undeclaredExactSetPack.externalManagedSetComponents()
	if err != nil {
		t.Fatal(err)
	}
	if len(managedSets) != 0 {
		t.Fatalf("undeclared source synthesized %d authoritative empty exact sets, want none", len(managedSets))
	}
}

func TestComponentRegistrationSetAllowsUnregisteredKindsAndRejectsDuplicateConfiguration(t *testing.T) {
	presentation := cp.ComponentKindRegistration{
		Contract:     cp.ComponentKindContract{Kind: cp.ComponentKindSearchScopeOrder},
		Capability:   cp.SourceBackedComponentCapability{Kind: cp.ComponentKindSearchScopeOrder, Installable: true},
		BuildHandler: func(cp.ComponentBuildContext, cp.ComponentBuildRequest) error { return nil },
	}
	if _, err := NewComponentKindRegistrationSet([]cp.ComponentKindRegistration{presentation}); err != nil {
		t.Fatalf("minimal external presentation registration: %v", err)
	}
	root := writePackFixture(t, "jira-like-sample", validPackManifest(`[
		`+formulaFunctionBundleManifestComponent(testFormulaFunctionBundleComponentPayload())+`
	]`, ``))
	sourceReport := ValidatePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	if sourceReport.HasErrors() {
		t.Fatalf("source validation failed: %s", sourceReport.Error())
	}
	option := WithComponentKindRegistrations([]cp.ComponentKindRegistration{
		testComponentKindRegistration(cp.ComponentKindApprovalProcess, "ProcessDefinition"),
	})
	if _, err := NewSourceBackedPack(sourceReport.Source, option); err != nil {
		t.Fatalf("unregistered manifest kind failed: %v", err)
	}
	if _, err := NewSourceBackedPack(sourceReport.Source, option, option); err == nil || !strings.Contains(err.Error(), "configured more than once") {
		t.Fatalf("duplicate configuration error = %v", err)
	}
}

func testComponentKindRegistration(kind cp.ComponentKind, table string) cp.ComponentKindRegistration {
	manageabilities := map[cp.Manageability]struct{}{cp.ManageabilityLocked: {}}
	return cp.ComponentKindRegistration{
		Contract: cp.ComponentKindContract{
			Kind: kind, Phase: cp.InstallPlanPhaseAutomation,
			WriteSet:    []cp.ComponentWriteSurface{{Table: table, Fact: "managed rows"}},
			BusinessKey: "component identity", OwnerDiscriminator: cp.ComponentOwnerStrategyOwnerPack,
			Manageabilities: manageabilities, VerifyProjection: "canonical rows", CollisionKey: "component identity",
		},
		Capability:        cp.SourceBackedComponentCapability{Kind: kind, Installable: true, Manageabilities: manageabilities},
		AllowedCategories: []cp.PackCategory{cp.AppPack},
		BuildHandler:      func(cp.ComponentBuildContext, cp.ComponentBuildRequest) error { return nil },
		ManagedProvider:   completeManagedComponentProvider(),
	}
}
