package serverruntime

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

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

type assignmentPolicyEligibilityProjectionProviderStub struct{}

func (assignmentPolicyEligibilityProjectionProviderStub) AssignmentPolicyEligibilityReadSurfaces() []contractcp.ComponentReadSurface {
	return nil
}

func (assignmentPolicyEligibilityProjectionProviderStub) ProjectAssignmentPolicyEligibility(
	context.Context,
	contractcp.ComponentManagedReadStore,
	[]string,
) (permissions.AssignmentPolicyEligibilityProjection, error) {
	return permissions.AssignmentPolicyEligibilityProjection{
		Resolution: permissions.AssignmentPolicyEligibilityResolved,
	}, nil
}

func TestAssignmentPolicyEligibilityHandlerFailsClosedOutsideExactPessimisticTransaction(t *testing.T) {
	tests := []struct {
		name         string
		transactions assignmentPolicyEligibilityTransaction
		want         string
	}{
		{name: "missing transaction manager", want: "transaction manager"},
		{name: "inactive", transactions: assignmentPolicyEligibilityTransactionStub{}, want: "active pessimistic transaction"},
		{name: "optimistic", transactions: assignmentPolicyEligibilityTransactionStub{active: true}, want: "active pessimistic transaction"},
		{name: "wrong transaction type", transactions: assignmentPolicyEligibilityTransactionStub{active: true, pessimistic: true, tx: "not-sql"}, want: "non-nil *sql.Tx"},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			handler := &assignmentPolicyEligibilityMutationHandler{
				transactions: test.transactions,
				stager:       &assignmentPolicyEligibilityAmbientStagerStub{},
			}
			err := handler.ApplyAssignmentPolicyEligibilityMutation(
				t.Context(), permissions.AssignmentPolicyEligibilityMutation{SourceIdentity: "user/005"},
			)
			if err == nil || !strings.Contains(err.Error(), test.want) {
				t.Fatalf("error = %v, want containing %q", err, test.want)
			}
		})
	}
}

func TestAssignmentPolicyEligibilityHandlerEmptyBatchIsNoOp(t *testing.T) {
	stager := &assignmentPolicyEligibilityAmbientStagerStub{}
	handler := &assignmentPolicyEligibilityMutationHandler{stager: stager}
	if err := handler.ApplyAssignmentPolicyEligibilityMutations(t.Context(), nil); err != nil {
		t.Fatal(err)
	}
	if stager.calls != 0 {
		t.Fatalf("ambient stage calls = %d, want 0", stager.calls)
	}
}

func TestAssignmentPolicyEligibilityDesiredKeysAreOpaqueUniqueAndCanonical(t *testing.T) {
	keys := assignmentPolicyEligibilityDesiredKeys([]permissions.AssignmentPolicyEligibilityMutation{
		{SourceIdentity: " user/one "},
		{SourceIdentity: "user/two"},
		{SourceIdentity: " user/one "},
	})
	if len(keys) != 2 || keys[0] == keys[1] {
		t.Fatalf("desired keys = %#v, want two unique opaque identities", keys)
	}
	for _, key := range keys {
		if strings.TrimSpace(key) != key || !strings.HasPrefix(key, "source/") {
			t.Fatalf("desired key = %q, want canonical encoded source key", key)
		}
	}
}

type assignmentPolicyEligibilityAmbientStagerStub struct {
	calls     int
	tx        *sql.Tx
	mutations []permissions.AssignmentPolicyEligibilityMutation
	err       error
}

func (s *assignmentPolicyEligibilityAmbientStagerStub) Stage(
	_ context.Context,
	tx *sql.Tx,
	mutations []permissions.AssignmentPolicyEligibilityMutation,
) error {
	s.calls++
	s.tx = tx
	s.mutations = append([]permissions.AssignmentPolicyEligibilityMutation(nil), mutations...)
	return s.err
}

type assignmentPolicyEligibilityTransactionStub struct {
	active      bool
	pessimistic bool
	tx          any
}

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

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

func (s assignmentPolicyEligibilityTransactionStub) ExtractTx(context.Context) interface{} {
	return s.tx
}
