package serverruntime

import (
	"bytes"
	"context"
	"database/sql"
	"testing"

	contractcp "github.com/pingcap-inc/nexuscrm-contract/pkg/controlplane"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/permissionstatev1"
	"github.com/pingcap-inc/nexuscrm-controlplane/packs"
)

func TestPermissionStateManagedLifecycleEffectAdapterBridgesCanonicalReceipt(t *testing.T) {
	receipt := testPermissionStateManagedLifecycleReceipt(t, 9, 10)
	effect := &testPermissionStateTransactionEffect{
		receipt: receipt,
		proved:  true,
	}
	adapter := permissionStateManagedLifecycleEffectAdapter(effect)
	if adapter.Class != PermissionStateManagedLifecycleEffectClass ||
		adapter.LockPolicy != packs.ManagedLifecycleEffectManagedStateOnly {
		t.Fatalf(
			"adapter identity = class %q lock policy %q, want %q/%q",
			adapter.Class, adapter.LockPolicy,
			PermissionStateManagedLifecycleEffectClass, packs.ManagedLifecycleEffectManagedStateOnly,
		)
	}
	if adapter.LockPolicy != packs.ManagedLifecycleEffectManagedStateOnly {
		t.Fatalf(
			"adapter lock policy = %q, want %q",
			adapter.LockPolicy,
			packs.ManagedLifecycleEffectManagedStateOnly,
		)
	}

	request := testPermissionStateManagedLifecycleRequest(
		contractcp.ComponentManagedMutationDesiredProjectionStaged,
	)
	tx := &sql.Tx{}
	encoded, err := adapter.Apply(context.Background(), tx, request)
	if err != nil {
		t.Fatalf("Apply(): %v", err)
	}
	wantEncoded, err := receipt.MarshalBinary()
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(encoded, wantEncoded) || effect.appliedTx != tx {
		t.Fatalf("Apply() = %x using %p, want %x using %p", encoded, effect.appliedTx, wantEncoded, tx)
	}

	db := &sql.DB{}
	proved, err := adapter.Prove(context.Background(), db, request, encoded)
	if err != nil {
		t.Fatalf("Prove(): %v", err)
	}
	if !proved || effect.provedDB != db {
		t.Fatalf("Prove() = %t using %p, want true using %p", proved, effect.provedDB, db)
	}
	gotReceipt, err := effect.provedReceipt.MarshalBinary()
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(gotReceipt, wantEncoded) {
		t.Fatalf("Prove() receipt = %x, want %x", gotReceipt, wantEncoded)
	}
}

func TestPermissionStateManagedLifecycleEffectAdapterRejectsNoChangeAndInvalidReceipt(t *testing.T) {
	effect := &testPermissionStateTransactionEffect{
		receipt: testPermissionStateManagedLifecycleReceipt(t, 9, 10),
		proved:  true,
	}
	adapter := permissionStateManagedLifecycleEffectAdapter(effect)
	noChange := testPermissionStateManagedLifecycleRequest(
		contractcp.ComponentManagedMutationNoChange,
	)
	if _, err := adapter.Apply(context.Background(), &sql.Tx{}, noChange); err == nil {
		t.Fatal("Apply(no change) returned nil error")
	}
	if effect.appliedTx != nil {
		t.Fatal("Apply(no change) reached the persistence effect")
	}

	changed := testPermissionStateManagedLifecycleRequest(
		contractcp.ComponentManagedMutationSafeProjectionStaged,
	)
	if proved, err := adapter.Prove(
		context.Background(),
		&sql.DB{},
		changed,
		[]byte("not-a-receipt"),
	); err == nil || proved {
		t.Fatalf("Prove(invalid receipt) = %t, %v; want false error", proved, err)
	}
	if effect.provedDB != nil {
		t.Fatal("Prove(invalid receipt) reached the persistence effect")
	}
}

func TestPermissionStateManagedLifecycleEffectAdapterAcceptsPlannedTransactionScope(t *testing.T) {
	effect := &testPermissionStateTransactionEffect{
		receipt: testPermissionStateManagedLifecycleReceipt(t, 9, 10),
	}
	adapter := permissionStateManagedLifecycleEffectAdapter(effect)
	request := testPermissionStateManagedLifecycleRequest(
		contractcp.ComponentManagedMutationDesiredProjectionStaged,
	)
	request.Scope = contractcp.ComponentManagedScopePlannedTransaction

	if _, err := adapter.Apply(context.Background(), &sql.Tx{}, request); err != nil {
		t.Fatalf("Apply(planned transaction): %v", err)
	}

	request.Scope = contractcp.ComponentManagedScope("ambient")
	if _, err := adapter.Apply(context.Background(), &sql.Tx{}, request); err == nil {
		t.Fatal("Apply(legacy ambient scope) returned nil error")
	}
}

func TestProductCompositionInstallsPermissionStateManagedLifecycleEffect(t *testing.T) {
	hooks, err := ComposeProductDistribution(NoopProductDistribution())
	if err != nil {
		t.Fatalf("ComposeProductDistribution(): %v", err)
	}
	set := hooks.ComponentKindRegistrationSet()
	effects := set.ManagedLifecycleEffectAdapters()
	if !set.ManagedLifecycleRuntimeConfigured() ||
		len(effects) != 1 ||
		effects[0].Class != PermissionStateManagedLifecycleEffectClass ||
		effects[0].LockPolicy != packs.ManagedLifecycleEffectManagedStateOnly {
		t.Fatalf("managed lifecycle effects = %#v, want Permission State v1", effects)
	}
	if effects[0].Apply == nil || effects[0].Prove == nil {
		t.Fatal("Permission State managed lifecycle effect callbacks are incomplete")
	}
}

type testPermissionStateTransactionEffect struct {
	receipt       permissionstatev1.AdvanceReceipt
	proved        bool
	appliedTx     *sql.Tx
	provedDB      *sql.DB
	provedReceipt permissionstatev1.AdvanceReceipt
}

func (effect *testPermissionStateTransactionEffect) Apply(
	_ context.Context,
	tx *sql.Tx,
) (permissionstatev1.AdvanceReceipt, error) {
	effect.appliedTx = tx
	return effect.receipt, nil
}

func (effect *testPermissionStateTransactionEffect) Prove(
	_ context.Context,
	db *sql.DB,
	receipt permissionstatev1.AdvanceReceipt,
) (bool, error) {
	effect.provedDB = db
	effect.provedReceipt = receipt
	return effect.proved, nil
}

func testPermissionStateManagedLifecycleRequest(
	disposition contractcp.ComponentManagedMutationDisposition,
) packs.ManagedLifecycleEffectRequest {
	return packs.ManagedLifecycleEffectRequest{
		OperationID: []byte("operation-1"),
		Attempt:     1,
		Kind:        contractcp.ComponentKindApprovalProcess,
		Scope:       contractcp.ComponentManagedScopeItem,
		Disposition: disposition,
	}
}

func testPermissionStateManagedLifecycleReceipt(
	t *testing.T,
	beforeRevision uint64,
	afterRevision uint64,
) permissionstatev1.AdvanceReceipt {
	t.Helper()
	lineage, err := permissionstatev1.LineageFromBytes(
		[]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
	)
	if err != nil {
		t.Fatal(err)
	}
	before, err := permissionstatev1.NewVersion(lineage, beforeRevision)
	if err != nil {
		t.Fatal(err)
	}
	after, err := permissionstatev1.NewVersion(lineage, afterRevision)
	if err != nil {
		t.Fatal(err)
	}
	receipt, err := permissionstatev1.NewAdvanceReceipt(before, after)
	if err != nil {
		t.Fatal(err)
	}
	return receipt
}
