package runtimeapi

import (
	"context"
	"errors"
	"testing"

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

type effectiveSnapshotLoaderStub struct {
	snapshot permissionstatev1.EffectiveSnapshot
	err      error
}

type permissionFloorFencerStub struct {
	err   error
	calls *int
}

func (stub permissionFloorFencerStub) FencePermissionFloorWithinTransaction(
	context.Context,
	permissionstatev1.Version,
) error {
	if stub.calls != nil {
		*stub.calls++
	}
	return stub.err
}

func (stub effectiveSnapshotLoaderStub) LoadCurrentEffectivePermissionSnapshot(
	context.Context,
	permissionstatev1.Subject,
) (permissionstatev1.EffectiveSnapshot, error) {
	return stub.snapshot, stub.err
}

func TestCurrentMailboxPermissionAuthorizerRequiresExactPermissionFloor(t *testing.T) {
	subject, err := permissionstatev1.NewSubject("user-1", "profile-1")
	if err != nil {
		t.Fatal(err)
	}
	floor := mailboxPermissionFloor(t, 11)
	matchingSnapshot, err := permissionstatev1.NewEffectiveSnapshot(
		floor,
		nil,
		[]string{"SendRecordEmail"},
	)
	if err != nil {
		t.Fatal(err)
	}
	fenceCalls := 0
	authorizer, err := newCurrentMailboxPermissionAuthorizer(
		effectiveSnapshotLoaderStub{snapshot: matchingSnapshot},
		permissionFloorFencerStub{calls: &fenceCalls},
	)
	if err != nil {
		t.Fatal(err)
	}
	if err := authorizer.AuthorizeMailboxSubjectWithinTransaction(
		context.Background(), subject, floor, MailboxPermissionRequirement{CustomPermission: "SendRecordEmail"},
	); err != nil {
		t.Fatalf("matching permission floor error = %v", err)
	}
	if fenceCalls != 1 {
		t.Fatalf("permission floor fence calls = %d, want 1", fenceCalls)
	}

	snapshot, err := permissionstatev1.NewEffectiveSnapshot(
		mailboxPermissionFloor(t, 12),
		nil,
		[]string{"SendRecordEmail"},
	)
	if err != nil {
		t.Fatal(err)
	}
	authorizer, err = newCurrentMailboxPermissionAuthorizer(
		effectiveSnapshotLoaderStub{snapshot: snapshot},
		permissionFloorFencerStub{},
	)
	if err != nil {
		t.Fatal(err)
	}
	if err := authorizer.AuthorizeMailboxSubject(
		context.Background(), subject, floor, MailboxPermissionRequirement{CustomPermission: "SendRecordEmail"},
	); !errors.Is(err, errMailboxAuthorizationUnavailable) {
		t.Fatalf("changed permission floor error = %v, want unavailable", err)
	}
}

func TestCurrentMailboxPermissionAuthorizerSystemPermission(t *testing.T) {
	subject, err := permissionstatev1.NewSubject("user-1", "profile-1")
	if err != nil {
		t.Fatal(err)
	}
	floor := mailboxPermissionFloor(t, 11)
	snapshot, err := permissionstatev1.NewEffectiveSnapshot(floor, []string{"ApiEnabled"}, nil)
	if err != nil {
		t.Fatal(err)
	}
	authorizer, err := newCurrentMailboxPermissionAuthorizer(effectiveSnapshotLoaderStub{snapshot: snapshot}, permissionFloorFencerStub{})
	if err != nil {
		t.Fatal(err)
	}
	if err := authorizer.AuthorizeMailboxSubject(context.Background(), subject, floor, MailboxPermissionRequirement{SystemPermission: "ApiEnabled"}); err != nil {
		t.Fatalf("system-permission mailbox audience was denied: %v", err)
	}
}

func TestMailboxPermissionNamespacesAndCallbackFence(t *testing.T) {
	subject, _ := permissionstatev1.NewSubject("user-1", "profile-1")
	floor := mailboxPermissionFloor(t, 11)
	for _, tc := range []struct {
		name           string
		requirement    MailboxPermissionRequirement
		system, custom []string
		version        permissionstatev1.Version
		allowed        bool
	}{
		{"system", MailboxPermissionRequirement{SystemPermission: "mail-access"}, []string{"mail-access"}, nil, floor, true},
		{"custom", MailboxPermissionRequirement{CustomPermission: "mail-access"}, nil, []string{"mail-access"}, floor, true},
		{"system cannot satisfy custom", MailboxPermissionRequirement{CustomPermission: "mail-access"}, []string{"mail-access"}, nil, floor, false},
		{"custom cannot satisfy system", MailboxPermissionRequirement{SystemPermission: "mail-access"}, nil, []string{"mail-access"}, floor, false},
		{"revoked", MailboxPermissionRequirement{SystemPermission: "mail-access"}, nil, nil, floor, false},
		{"stale floor", MailboxPermissionRequirement{SystemPermission: "mail-access"}, []string{"mail-access"}, nil, mailboxPermissionFloor(t, 12), false},
		{"empty", MailboxPermissionRequirement{}, []string{"mail-access"}, []string{"mail-access"}, floor, false},
		{"ambiguous", MailboxPermissionRequirement{SystemPermission: "mail-access", CustomPermission: "mail-access"}, []string{"mail-access"}, []string{"mail-access"}, floor, false},
	} {
		t.Run(tc.name, func(t *testing.T) {
			snapshot, err := permissionstatev1.NewEffectiveSnapshot(tc.version, tc.system, tc.custom)
			if err != nil {
				t.Fatal(err)
			}
			fences := 0
			authorizer, err := newCurrentMailboxPermissionAuthorizer(effectiveSnapshotLoaderStub{snapshot: snapshot}, permissionFloorFencerStub{calls: &fences})
			if err != nil {
				t.Fatal(err)
			}
			for _, authorize := range []func(context.Context, permissionstatev1.Subject, permissionstatev1.Version, MailboxPermissionRequirement) error{authorizer.AuthorizeMailboxSubject, authorizer.AuthorizeMailboxSubjectWithinTransaction} {
				if err := authorize(t.Context(), subject, floor, tc.requirement); (err == nil) != tc.allowed {
					t.Fatalf("allowed=%v, error=%v", tc.allowed, err)
				}
			}
			if (fences == 1) != tc.allowed {
				t.Fatalf("denied callback must not fence; fences=%d", fences)
			}
		})
	}
}
