package serverruntime

import (
	"context"
	"errors"
	"strings"
	"testing"
	"time"

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

func TestComposeProductDistributionOwnsImmutableComponentKindRegistrationSet(t *testing.T) {
	providerCalls := 0
	provider := func() (contractcp.ComponentKindRegistration, error) {
		providerCalls++
		return testProductComponentKindRegistration(contractcp.ComponentKindApprovalProcess), nil
	}

	hooks, err := ComposeProductDistribution(NewProductDistribution(ProductHooks{
		Capabilities: ProductCapabilities{
			ComponentKindRegistrationProviders: []ComponentKindRegistrationProvider{provider},
		},
	}))
	if err != nil {
		t.Fatalf("ComposeProductDistribution: %v", err)
	}
	if providerCalls != 1 {
		t.Fatalf("component kind registration provider calls = %d, want 1", providerCalls)
	}

	registrations := hooks.ComponentKindRegistrationSet().Registrations()
	if len(registrations) != 1 ||
		!testProductHasComponentKind(registrations, contractcp.ComponentKindApprovalProcess) {
		t.Fatalf("composed component kind registrations = %#v, want approval process only", registrations)
	}
	for index := range registrations {
		registrations[index].Contract.Kind = "mutated"
	}
	got := hooks.ComponentKindRegistrationSet().Registrations()
	if len(got) != 1 ||
		!testProductHasComponentKind(got, contractcp.ComponentKindApprovalProcess) {
		t.Fatalf("composed registration snapshot mutated through read copy: %#v", got)
	}

	noop, err := ComposeProductDistribution(NoopProductDistribution())
	if err != nil {
		t.Fatalf("ComposeProductDistribution(noop): %v", err)
	}
	if got := noop.ComponentKindRegistrationSet().Registrations(); len(got) != 0 {
		t.Fatalf("noop component kind registrations = %#v, want none", got)
	}
}

func TestComposeProductDistributionOwnsManagedLifecycleRegistration(t *testing.T) {
	providerCalls := 0
	provider := func() (contractcp.ComponentKindRegistration, contractcp.ComponentManagedLifecycleRegistration, error) {
		providerCalls++
		registration := testProductManagedLifecycleBaseRegistration(contractcp.ComponentKindApprovalProcess, "ProcessDefinition")
		return registration, testProductComponentManagedLifecycleRegistration(t, contractcp.ComponentKindApprovalProcess, "ProcessDefinition"), nil
	}

	hooks, err := ComposeProductDistribution(NewProductDistribution(ProductHooks{
		Capabilities: ProductCapabilities{
			ComponentKindManagedLifecycleRegistrationProviders: []ComponentKindManagedLifecycleRegistrationProvider{provider},
		},
	}))
	if err != nil {
		t.Fatalf("ComposeProductDistribution: %v", err)
	}
	if providerCalls != 1 {
		t.Fatalf("managed lifecycle registration provider calls = %d, want 1", providerCalls)
	}

	set := hooks.ComponentKindRegistrationSet()
	registrations := set.Registrations()
	if len(registrations) != 1 ||
		!testProductHasComponentKind(registrations, contractcp.ComponentKindApprovalProcess) {
		t.Fatalf("composed component kind registrations = %#v, want approval process only", registrations)
	}
	for _, registration := range registrations {
		if registration.ManagedProvider != nil {
			t.Fatal("managed lifecycle composition retained the legacy provider path")
		}
	}
	lifecycles := set.ManagedLifecycles()
	if len(lifecycles) != 1 ||
		!testProductHasManagedLifecycleKind(lifecycles, contractcp.ComponentKindApprovalProcess) {
		t.Fatalf("composed managed lifecycles = %#v, want approval process only", lifecycles)
	}
}

func TestPlatformPermissionSetDefinitionEffectClassMatchesRuntimeAdapter(t *testing.T) {
	if permissions.PermissionSetDefinitionPermissionStateEffectClass !=
		PermissionStateManagedLifecycleEffectClass {
		t.Fatalf(
			"permission set provider effect class = %q, runtime adapter = %q",
			permissions.PermissionSetDefinitionPermissionStateEffectClass,
			PermissionStateManagedLifecycleEffectClass,
		)
	}
}

func TestComposeProductDistributionRejectsInvalidComponentKindRegistrationProviders(t *testing.T) {
	failingProvider := func() (contractcp.ComponentKindRegistration, error) {
		return contractcp.ComponentKindRegistration{}, errors.New("provider failed")
	}
	tests := []struct {
		name      string
		products  []ProductHooks
		wantError string
	}{
		{
			name: "nil provider",
			products: []ProductHooks{{Capabilities: ProductCapabilities{
				ComponentKindRegistrationProviders: []ComponentKindRegistrationProvider{nil},
			}}},
			wantError: "provider[0] is nil",
		},
		{
			name: "provider error",
			products: []ProductHooks{{Capabilities: ProductCapabilities{
				ComponentKindRegistrationProviders: []ComponentKindRegistrationProvider{failingProvider},
			}}},
			wantError: "provider failed",
		},
		{
			name: "active legacy managed provider",
			products: []ProductHooks{{Capabilities: ProductCapabilities{
				ComponentKindRegistrationProviders: []ComponentKindRegistrationProvider{
					func() (contractcp.ComponentKindRegistration, error) {
						registration := testProductManagedLifecycleBaseRegistration(contractcp.ComponentKindApprovalProcess, "ProcessDefinition")
						registration.ManagedProvider = testProductLegacyManagedProvider()
						return registration, nil
					},
				},
			}}},
			wantError: "active legacy managed providers are forbidden",
		},
		{
			name: "duplicate kind",
			products: []ProductHooks{
				{Capabilities: ProductCapabilities{ComponentKindRegistrationProviders: []ComponentKindRegistrationProvider{
					func() (contractcp.ComponentKindRegistration, error) {
						return testProductComponentKindRegistration(contractcp.ComponentKindApprovalProcess), nil
					},
				}}},
				{Capabilities: ProductCapabilities{ComponentKindRegistrationProviders: []ComponentKindRegistrationProvider{
					func() (contractcp.ComponentKindRegistration, error) {
						return testProductComponentKindRegistration(contractcp.ComponentKindApprovalProcess), nil
					},
				}}},
			},
			wantError: "registered more than once",
		},
		{
			name: "competing write table",
			products: []ProductHooks{
				{Capabilities: ProductCapabilities{ComponentKindRegistrationProviders: []ComponentKindRegistrationProvider{
					func() (contractcp.ComponentKindRegistration, error) {
						return testProductComponentKindRegistrationWithTable(contractcp.ComponentKindApprovalProcess, "SharedWriteTable"), nil
					},
				}}},
				{Capabilities: ProductCapabilities{ComponentKindRegistrationProviders: []ComponentKindRegistrationProvider{
					func() (contractcp.ComponentKindRegistration, error) {
						return testProductComponentKindRegistrationWithTable(contractcp.ComponentKindUIAction, "SharedWriteTable"), nil
					},
				}}},
			},
			wantError: "both claim write table",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := ComposeProductDistribution(NewProductDistribution(tt.products...))
			if err == nil || !strings.Contains(err.Error(), tt.wantError) {
				t.Fatalf("ComposeProductDistribution error = %v, want containing %q", err, tt.wantError)
			}
		})
	}
}

func TestComposeProductDistributionRejectsInvalidManagedLifecycleRegistrationProviders(t *testing.T) {
	failingProvider := func() (contractcp.ComponentKindRegistration, contractcp.ComponentManagedLifecycleRegistration, error) {
		return contractcp.ComponentKindRegistration{}, contractcp.ComponentManagedLifecycleRegistration{}, errors.New("provider failed")
	}
	lifecycleProvider := func(kind contractcp.ComponentKind) ComponentKindManagedLifecycleRegistrationProvider {
		return func() (contractcp.ComponentKindRegistration, contractcp.ComponentManagedLifecycleRegistration, error) {
			return testProductManagedLifecycleBaseRegistration(kind, "ProcessDefinition"), testProductComponentManagedLifecycleRegistration(t, kind, "ProcessDefinition"), nil
		}
	}
	tests := []struct {
		name      string
		products  []ProductHooks
		wantError string
	}{
		{
			name: "nil provider",
			products: []ProductHooks{{Capabilities: ProductCapabilities{
				ComponentKindManagedLifecycleRegistrationProviders: []ComponentKindManagedLifecycleRegistrationProvider{nil},
			}}},
			wantError: "provider[0] is nil",
		},
		{
			name: "provider error",
			products: []ProductHooks{{Capabilities: ProductCapabilities{
				ComponentKindManagedLifecycleRegistrationProviders: []ComponentKindManagedLifecycleRegistrationProvider{failingProvider},
			}}},
			wantError: "provider failed",
		},
		{
			name: "duplicate kind across registration generations",
			products: []ProductHooks{{Capabilities: ProductCapabilities{
				ComponentKindRegistrationProviders: []ComponentKindRegistrationProvider{
					func() (contractcp.ComponentKindRegistration, error) {
						return testProductComponentKindRegistration(contractcp.ComponentKindApprovalProcess), nil
					},
				},
				ComponentKindManagedLifecycleRegistrationProviders: []ComponentKindManagedLifecycleRegistrationProvider{
					lifecycleProvider(contractcp.ComponentKindApprovalProcess),
				},
			}}},
			wantError: "registered more than once",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := ComposeProductDistribution(NewProductDistribution(tt.products...))
			if err == nil || !strings.Contains(err.Error(), tt.wantError) {
				t.Fatalf("ComposeProductDistribution error = %v, want containing %q", err, tt.wantError)
			}
		})
	}
}

type ambientComponentStoreTransactionStub struct {
	active      bool
	pessimistic bool
	transaction interface{}
}

func (s ambientComponentStoreTransactionStub) IsTransactionActive(context.Context) bool {
	return s.active
}

func (s ambientComponentStoreTransactionStub) IsPessimisticTx(context.Context) bool {
	return s.pessimistic
}

func (s ambientComponentStoreTransactionStub) ExtractTx(context.Context) interface{} {
	return s.transaction
}

func testProductComponentKindRegistration(kind contractcp.ComponentKind) contractcp.ComponentKindRegistration {
	return testProductComponentKindRegistrationWithTable(kind, "")
}

func testProductHasComponentKind(
	registrations []contractcp.ComponentKindRegistration,
	kind contractcp.ComponentKind,
) bool {
	for _, registration := range registrations {
		if registration.Contract.Kind == kind {
			return true
		}
	}
	return false
}

func testProductHasManagedLifecycleKind(
	lifecycles []contractcp.ComponentKindManagedLifecycleRegistration,
	kind contractcp.ComponentKind,
) bool {
	for _, lifecycle := range lifecycles {
		if lifecycle.ManagedLifecycle().Kind() == kind {
			return true
		}
	}
	return false
}

func testProductComponentKindRegistrationWithTable(kind contractcp.ComponentKind, table string) contractcp.ComponentKindRegistration {
	registration := contractcp.ComponentKindRegistration{
		Contract: contractcp.ComponentKindContract{Kind: kind},
		Capability: contractcp.SourceBackedComponentCapability{
			Kind:        kind,
			Installable: true,
		},
		BuildHandler: func(contractcp.ComponentBuildContext, contractcp.ComponentBuildRequest) error { return nil },
	}
	if table != "" {
		registration.Contract.WriteSet = []contractcp.ComponentWriteSurface{{Table: table}}
	}
	return registration
}

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

func testProductLegacyManagedProvider() *contractcp.ComponentManagedProvider {
	return &contractcp.ComponentManagedProvider{
		Preflight: func(context.Context, contractcp.ComponentRowReader, contractcp.ComponentManagedRequest) error {
			return nil
		},
		Apply: func(context.Context, contractcp.ComponentMutationStore, contractcp.ComponentManagedRequest) error {
			return nil
		},
		VerifyProjection: func(context.Context, contractcp.ComponentRowReader, contractcp.ComponentManagedRequest) ([]byte, []byte, error) {
			return nil, nil, nil
		},
		ListLiveBusinessKeys: func(context.Context, contractcp.ComponentRowReader, contractcp.ComponentManagedSetRequest) ([]contractcp.ComponentLiveBusinessKey, error) {
			return nil, nil
		},
		ReconcileSet: func(context.Context, contractcp.ComponentMutationStore, contractcp.ComponentManagedSetRequest, []string) error {
			return nil
		},
	}
}

func testProductComponentManagedLifecycleRegistration(t *testing.T, kind contractcp.ComponentKind, table string) contractcp.ComponentManagedLifecycleRegistration {
	t.Helper()
	registration, err := contractcp.NewComponentManagedLifecycleRegistration(contractcp.ComponentManagedLifecycleRegistrationSpec{
		Kind:    kind,
		ReadSet: []contractcp.ComponentReadSurface{{Table: table, Fact: "managed rows"}},
		Capabilities: []contractcp.ComponentManagedCapability{
			contractcp.ComponentManagedCapabilityTargetSerialization,
			contractcp.ComponentManagedCapabilityStructuredMutation,
			contractcp.ComponentManagedCapabilityEvidence,
			contractcp.ComponentManagedCapabilityFreshProjection,
		},
		TransactionMode: contractcp.ComponentManagedTransactionPessimisticReadCommitted,
		Budget: contractcp.ComponentManagedBudget{
			MaxTargets: 10, MaxCurrentItems: 10, MaxDesiredItems: 10, MaxDeltaRows: 10,
			MaxMutationBytes: 1024, MaxMutationStatements: 10, MaxBatchRows: 10,
			MaxEvidenceWrites: 1, MaxEffectCalls: 1,
			MaxLockWait: time.Second, MaxElapsed: time.Second, MaxAttempts: 3,
		},
		Lifecycle: contractcp.ComponentManagedLifecycleProvider{
			Plan: func(context.Context, contractcp.ComponentManagedScope, contractcp.ComponentManagedLifecycleRequest, contractcp.ComponentManagedReadStore) (contractcp.ComponentManagedPlan, error) {
				return contractcp.ComponentManagedPlan{}, nil
			},
			Mutate: func(context.Context, contractcp.ComponentManagedScope, contractcp.ComponentManagedLifecycleRequest, contractcp.ComponentManagedPlan, contractcp.ComponentManagedMutationStore) (contractcp.ComponentManagedMutationResult, error) {
				return contractcp.ComponentManagedMutationResult{}, nil
			},
			Project: func(context.Context, contractcp.ComponentManagedScope, contractcp.ComponentManagedLifecycleRequest, contractcp.ComponentManagedReadStore) (contractcp.ComponentManagedProjection, error) {
				return contractcp.ComponentManagedProjection{}, nil
			},
		},
	})
	if err != nil {
		t.Fatal(err)
	}
	return registration
}
