package packs

import (
	"bytes"
	"context"
	"slices"
	"strings"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	cp "github.com/pingcap-inc/nexuscrm-contract/pkg/controlplane"
)

func TestRegisteredLegacyManagedRouteRetainsDescriptorAndFailsClosedBeforeApply(t *testing.T) {
	descriptor := cp.ComponentBuildManagedComponent{
		ExpectedProjection: []byte(`{"rows":[]}`),
		CollisionClaims:    []cp.ComponentCollisionClaim{{Namespace: "test", Key: "case/close"}},
	}
	registration := testComponentKindRegistration(cp.ComponentKindUIAction, "StandardAction")
	registration.Contract.Phase = cp.InstallPlanPhase("automation")
	order := []string{}
	registration.ManagedProvider.Preflight = func(_ context.Context, _ cp.ComponentRowReader, request cp.ComponentManagedRequest) error {
		order = append(order, "managed-preflight")
		request.Component.Payload[0] = 'x'
		request.Component.DependsOn[0].BusinessKey = "mutated"
		request.Payload[0] = 'x'
		request.ExpectedProjection[0] = 'x'
		return nil
	}
	registration.ManagedProvider.Apply = func(_ context.Context, _ cp.ComponentMutationStore, request cp.ComponentManagedRequest) error {
		order = append(order, "managed-apply")
		return nil
	}
	verifyCalls := 0
	registration.ManagedProvider.VerifyProjection = func(_ context.Context, _ cp.ComponentRowReader, request cp.ComponentManagedRequest) ([]byte, []byte, error) {
		verifyCalls++
		request.Component.Payload[0] = 'x'
		request.Component.DependsOn[0].BusinessKey = "mutated"
		request.Payload[0] = 'x'
		request.ExpectedProjection[0] = 'x'
		return []byte(`{"rows":[]}`), []byte(`{"rows":[]}`), nil
	}
	registration.BuildHandler = func(buildContext cp.ComponentBuildContext, _ cp.ComponentBuildRequest) error {
		return buildContext.AddManagedComponent(descriptor)
	}
	pack, err := NewSourceBackedPack(
		registeredManagedRouteTestSource(),
		WithComponentKindRegistrations([]cp.ComponentKindRegistration{registration}),
	)
	if err != nil {
		t.Fatal(err)
	}
	components := pack.Components()
	if len(components) != 1 {
		t.Fatalf("components = %d, want 1", len(components))
	}
	managed, ok := components[0].(*sourceBackedExternalManagedComponent)
	if !ok {
		t.Fatalf("component type = %T", components[0])
	}
	managed.manifestComponent.DependsOn = []ComponentReference{{
		Kind: ComponentKindObjectSpec, BusinessKey: "earlier", Required: true,
	}}
	descriptor.ExpectedProjection[0] = 'x'
	if got := managed.descriptor.ExpectedProjection; !bytes.Equal(got, []byte(`{"rows":[]}`)) {
		t.Fatalf("retained projection = %q", got)
	}
	if len(managed.descriptor.CollisionClaims) != 1 || managed.descriptor.CollisionClaims[0].Key != "case/close" {
		t.Fatalf("retained collision claims = %#v", managed.descriptor.CollisionClaims)
	}
	plan, err := NewInstallPlan([]PackComponent{managed})
	if err != nil {
		t.Fatal(err)
	}
	if got := plan.steps[InstallPlanPhaseAutomation]; !slices.Contains(got, PackComponent(managed)) {
		t.Fatalf("registered automation steps = %#v", got)
	}
	plan.phases = append([]InstallPlanPhase{InstallPlanPhaseObjectMetadata}, plan.phases...)
	plan.steps[InstallPlanPhaseObjectMetadata] = []PackComponent{&fakePlanComponent{kind: ComponentKindObjectSpec, key: "earlier", applied: &order}}
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = db.Close() })
	if err := plan.Apply(context.Background(), ApplyDeps{DB: db}, InstallPlanHooks{}); err == nil || !strings.Contains(err.Error(), "legacy managed provider execution is disabled") {
		t.Fatalf("Apply() error = %v", err)
	}
	if len(order) != 0 {
		t.Fatalf("callbacks/components ran before fail-closed barrier: %v", order)
	}
	for range 2 {
		if err := managed.VerifyComponent(context.Background(), VerifyDeps{DB: db}); err == nil || !strings.Contains(err.Error(), "legacy managed provider verification is disabled") {
			t.Fatalf("VerifyComponent() error = %v", err)
		}
	}
	if verifyCalls != 0 {
		t.Fatalf("deprecated verify callbacks = %d, want 0", verifyCalls)
	}
	if got := managed.manifestComponent.Payload; !bytes.Equal(got, []byte(`{}`)) {
		t.Fatalf("retained manifest payload after verify = %q", got)
	}
	if got := managed.descriptor.ExpectedProjection; !bytes.Equal(got, []byte(`{"rows":[]}`)) {
		t.Fatalf("retained expected projection after verify = %q", got)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatal(err)
	}
}

func TestRegisteredLegacyManagedRouteVerificationFailsClosed(t *testing.T) {
	registration := testComponentKindRegistration(cp.ComponentKindUIAction, "StandardAction")
	verifyCalls := 0
	registration.ManagedProvider.VerifyProjection = func(context.Context, cp.ComponentRowReader, cp.ComponentManagedRequest) ([]byte, []byte, error) {
		verifyCalls++
		return []byte(`{"rows":[]}`), []byte(`{"rows":[]}`), nil
	}
	component := newSourceBackedExternalManagedComponent("test-pack", ManifestComponent{
		Kind: ComponentKindUIAction, BusinessKey: "Case/Close", Manageability: ManageabilityLocked,
	}, []byte(`{}`), cp.ComponentBuildManagedComponent{ExpectedProjection: []byte(`{"rows":[]}`)}, registration)
	db, _, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = db.Close() })
	if err := component.VerifyComponent(context.Background(), VerifyDeps{DB: db}); err == nil || !strings.Contains(err.Error(), "legacy managed provider verification is disabled") {
		t.Fatalf("VerifyComponent() error = %v", err)
	}
	if verifyCalls != 0 {
		t.Fatalf("deprecated verify callbacks = %d, want 0", verifyCalls)
	}
}

func TestManagedInstallPlanPreflightBarrierFailsClosed(t *testing.T) {
	component := func(key, claim string, registration cp.ComponentKindRegistration) *sourceBackedExternalManagedComponent {
		return newSourceBackedExternalManagedComponent("test-pack", ManifestComponent{
			Kind: ComponentKindUIAction, BusinessKey: key, Manageability: ManageabilityLocked, Payload: []byte(`{}`),
		}, []byte(`{}`), cp.ComponentBuildManagedComponent{
			ExpectedProjection: []byte(`{}`),
			CollisionClaims:    []cp.ComponentCollisionClaim{{Namespace: "test", Key: claim}},
		}, registration)
	}

	t.Run("duplicate collision claim", func(t *testing.T) {
		callbacks := 0
		registration := testComponentKindRegistration(cp.ComponentKindUIAction, "StandardAction")
		registration.ManagedProvider.Preflight = func(context.Context, cp.ComponentRowReader, cp.ComponentManagedRequest) error {
			callbacks++
			return nil
		}
		plan, err := NewInstallPlan([]PackComponent{
			component("Case/One", "same", registration), component("Case/Two", "same", registration),
		})
		if err != nil {
			t.Fatal(err)
		}
		err = plan.Apply(context.Background(), ApplyDeps{}, InstallPlanHooks{})
		if err == nil || !strings.Contains(err.Error(), "legacy managed provider execution is disabled") || callbacks != 0 {
			t.Fatalf("duplicate claim error/callbacks = %v/%d", err, callbacks)
		}
	})

	t.Run("later preflight failure", func(t *testing.T) {
		events := []string{}
		registration := testComponentKindRegistration(cp.ComponentKindUIAction, "StandardAction")
		registration.ManagedProvider.Preflight = func(_ context.Context, _ cp.ComponentRowReader, request cp.ComponentManagedRequest) error {
			events = append(events, "preflight:"+request.Component.BusinessKey)
			if request.Component.BusinessKey == "Case/Two" {
				return context.Canceled
			}
			return nil
		}
		registration.ManagedProvider.Apply = func(_ context.Context, _ cp.ComponentMutationStore, request cp.ComponentManagedRequest) error {
			events = append(events, "apply:"+request.Component.BusinessKey)
			return nil
		}
		plan, err := NewInstallPlan([]PackComponent{
			&fakePlanComponent{kind: ComponentKindObjectSpec, key: "earlier", applied: &events},
			component("Case/One", "one", registration), component("Case/Two", "two", registration),
		})
		if err != nil {
			t.Fatal(err)
		}
		db, _, err := sqlmock.New()
		if err != nil {
			t.Fatal(err)
		}
		t.Cleanup(func() { _ = db.Close() })
		err = plan.Apply(context.Background(), ApplyDeps{DB: db}, InstallPlanHooks{AfterPhase: func(context.Context, InstallPlanPhase) error {
			events = append(events, "hook")
			return nil
		}})
		want := []string{}
		if err == nil || !strings.Contains(err.Error(), "legacy managed provider execution is disabled") || !slices.Equal(events, want) {
			t.Fatalf("preflight error/events = %v/%v, want %v", err, events, want)
		}
	})

	t.Run("source owner is explicit", func(t *testing.T) {
		registration := testComponentKindRegistration(cp.ComponentKindUIAction, "StandardAction")
		registration.Contract.OwnerDiscriminator = cp.ComponentOwnerStrategySource
		_, err := component("Case/One", "one", registration).managedRequest()
		if err == nil || !strings.Contains(err.Error(), "has no explicit source owner") {
			t.Fatalf("source owner error = %v", err)
		}
	})
}

func TestComponentRegistrationSetRejectsUnsupportedInstallPhase(t *testing.T) {
	registration := testComponentKindRegistration(cp.ComponentKindUIAction, "StandardAction")
	registration.Contract.Phase = cp.InstallPlanPhase("unknown")
	if _, err := NewComponentKindRegistrationSet([]cp.ComponentKindRegistration{registration}); err == nil || !strings.Contains(err.Error(), "phase \"unknown\" is unsupported") {
		t.Fatalf("phase error = %v", err)
	}
}

func TestInstallPlanRejectsRegisteredPhaseBeforeDeclaredDependency(t *testing.T) {
	registration := testComponentKindRegistration(cp.ComponentKindApprovalProcess, "ProcessDefinition")
	registration.Contract.Phase = cp.InstallPlanPhase("automation")
	managed := newSourceBackedExternalManagedComponent("test-pack", ManifestComponent{
		Kind: ComponentKindApprovalProcess, BusinessKey: "Quote/Quote_Approval",
		DependsOn: []ComponentReference{{Kind: ComponentKindUIAction, BusinessKey: "Quote/Close"}},
	}, nil, cp.ComponentBuildManagedComponent{ExpectedProjection: []byte(`{}`)}, registration)
	components := []PackComponent{
		managed,
		&fakePlanComponent{kind: ComponentKindUIAction, key: "Quote/Close"},
	}
	if _, err := NewInstallPlan(components); err == nil ||
		!strings.Contains(err.Error(), `install phase "automation"`) ||
		!strings.Contains(err.Error(), `later phase "ui_surfaces"`) {
		t.Fatalf("phase dependency error = %v", err)
	}
}

func TestRegisteredManagedRouteRejectsMissingDuplicateAndCompetingDescriptors(t *testing.T) {
	tests := []struct {
		name    string
		handler cp.ComponentBuildHandler
		want    string
	}{
		{name: "missing", handler: func(cp.ComponentBuildContext, cp.ComponentBuildRequest) error { return nil }, want: "added 0 managed descriptors"},
		{name: "duplicate swallowed", handler: func(buildContext cp.ComponentBuildContext, _ cp.ComponentBuildRequest) error {
			descriptor := cp.ComponentBuildManagedComponent{ExpectedProjection: []byte(`{}`)}
			_ = buildContext.AddManagedComponent(descriptor)
			_ = buildContext.AddManagedComponent(descriptor)
			return nil
		}, want: "more than one managed descriptor"},
		{name: "managed and legacy", handler: func(buildContext cp.ComponentBuildContext, request cp.ComponentBuildRequest) error {
			if err := buildContext.AddManagedComponent(cp.ComponentBuildManagedComponent{ExpectedProjection: []byte(`{}`)}); err != nil {
				return err
			}
			presentation := buildContext.(cp.PresentationComponentBuildContext)
			return presentation.AddUIActionComponent(request, nil)
		}, want: "cannot emit legacy UI-action component"},
		{name: "swallowed shallow output", handler: func(buildContext cp.ComponentBuildContext, _ cp.ComponentBuildRequest) error {
			_ = buildContext.AddRuntimeStartupShallowDeclaration(cp.ComponentKindUIAction, "Case/Close", []byte(`{}`))
			_ = buildContext.AddManagedComponent(cp.ComponentBuildManagedComponent{ExpectedProjection: []byte(`{}`)})
			return nil
		}, want: "cannot emit legacy runtime-startup shallow declaration"},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			registration := testComponentKindRegistration(cp.ComponentKindUIAction, "StandardAction")
			registration.BuildHandler = test.handler
			_, err := NewSourceBackedPack(registeredManagedRouteTestSource(), WithComponentKindRegistrations([]cp.ComponentKindRegistration{registration}))
			if err == nil || !strings.Contains(err.Error(), test.want) {
				t.Fatalf("legacy registration error = %v, want %q", err, test.want)
			}
			registration.ManagedProvider = nil
			lifecycle := testComponentManagedLifecycleRegistration(t, cp.ComponentKindUIAction, "StandardAction")
			bound, err := registration.WithManagedLifecycle(lifecycle)
			if err != nil {
				t.Fatal(err)
			}
			_, err = NewSourceBackedPack(registeredManagedRouteTestSource(), WithComponentKindManagedLifecycles(nil, []cp.ComponentKindManagedLifecycleRegistration{bound}))
			if err == nil || !strings.Contains(err.Error(), test.want) {
				t.Fatalf("lifecycle registration error = %v, want %q", err, test.want)
			}
		})
	}
	registration := testComponentKindRegistration(cp.ComponentKindUIAction, "StandardAction")
	registration.BuildHandler = func(cp.ComponentBuildContext, cp.ComponentBuildRequest) error { return nil }
	competingRoute := func(pack *SourceBackedPack) {
		pack.externalBuildRoutes = map[ComponentKind]externalBuildRoute{ComponentKindUIAction: {handler: registration.BuildHandler}}
	}
	if _, err := NewSourceBackedPack(registeredManagedRouteTestSource(), competingRoute, WithComponentKindRegistrations([]cp.ComponentKindRegistration{registration})); err == nil || !strings.Contains(err.Error(), "more than one external build route") {
		t.Fatalf("competing route error = %v", err)
	}
	wrongCategory := registeredManagedRouteTestSource()
	wrongCategory.Manifest.Category = ConnectorPack
	if _, err := NewSourceBackedPack(wrongCategory, WithComponentKindRegistrations([]cp.ComponentKindRegistration{registration})); err == nil || !strings.Contains(err.Error(), "does not support pack category") {
		t.Fatalf("category error = %v", err)
	}
}

func TestRegisteredManagedRouteRejectsSwallowedLegacySinkBeforeMutation(t *testing.T) {
	var building *SourceBackedPack
	registration := testComponentKindRegistration(cp.ComponentKindUIAction, "StandardAction")
	registration.BuildHandler = func(buildContext cp.ComponentBuildContext, request cp.ComponentBuildRequest) error {
		building = buildContext.(*managedOnlyComponentBuildContext).delegate.p
		presentation := buildContext.(cp.PresentationComponentBuildContext)
		_ = presentation.AddSearchScopeOrderComponent(request, []cp.SearchScopeOrderItem{{Name: "Unowned"}})
		_ = buildContext.AddManagedComponent(cp.ComponentBuildManagedComponent{ExpectedProjection: []byte(`{}`)})
		return nil
	}
	_, err := NewSourceBackedPack(registeredManagedRouteTestSource(), WithComponentKindRegistrations([]cp.ComponentKindRegistration{registration}))
	if err == nil || !strings.Contains(err.Error(), "cannot emit legacy search-scope-order component") {
		t.Fatalf("error = %v", err)
	}
	if building == nil || len(building.searchScopeOrder) != 0 {
		t.Fatalf("search-scope state leaked before rejection: %#v", building)
	}
}

func registeredManagedRouteTestSource() *ResolvedPackSource {
	return &ResolvedPackSource{
		Manifest: &PackManifest{
			Name: "test-pack", Category: AppPack,
			Components: []ManifestComponent{{
				Kind: ComponentKindUIAction, BusinessKey: "Case/Close",
				Manageability: ManageabilityLocked, Payload: []byte(`{}`),
			}},
		},
	}
}
