package runtimegrpc

import (
	"context"
	"sync"
	"sync/atomic"
	"testing"
	"time"

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

func TestIdentityBurstCacheCollapsesOnePageBurst(t *testing.T) {
	cache := newIdentityBurstCache()
	now := time.Date(2026, 8, 12, 1, 0, 0, 0, time.UTC)
	cache.now = func() time.Time { return now }
	request := identitysemantic.Request{
		AppInstallationID:     "01900000-0000-7000-8000-000000000001",
		RequestedCapabilities: []string{"runtime.permissions.effective.read.v1"},
	}

	var calls atomic.Int64
	started := make(chan struct{})
	release := make(chan struct{})
	exchange := func(context.Context, string, identitysemantic.Request) (identitysemantic.Response, error) {
		if calls.Add(1) == 1 {
			close(started)
		}
		<-release
		return identitysemantic.Response{
			OpaqueDelegation: "header.payload.signature", ExpiresAtUnixSecond: now.Add(time.Minute).Unix(),
		}, nil
	}

	const readers = 12
	var wait sync.WaitGroup
	wait.Add(readers)
	errs := make(chan error, readers)
	for range readers {
		go func() {
			defer wait.Done()
			response, err := cache.exchange(context.Background(), "bearer-a", request, exchange)
			if err == nil && response.OpaqueDelegation != "header.payload.signature" {
				err = &identityBurstTestError{"unexpected delegation"}
			}
			errs <- err
		}()
	}
	<-started
	close(release)
	wait.Wait()
	close(errs)
	for err := range errs {
		if err != nil {
			t.Fatal(err)
		}
	}
	if got := calls.Load(); got != 1 {
		t.Fatalf("identity exchanges = %d, want 1", got)
	}

	if _, err := cache.exchange(context.Background(), "bearer-a", request, exchange); err != nil {
		t.Fatal(err)
	}
	if got := calls.Load(); got != 1 {
		t.Fatalf("within-burst identity exchanges = %d, want 1", got)
	}

	// Runtime signed this delegation for one minute. Reissuing it before that
	// lifetime ends only repeats identity/session/security reads; every data RPC
	// independently rechecks those live authorities.
	now = now.Add(30 * time.Second)
	if _, err := cache.exchange(context.Background(), "bearer-a", request, exchange); err != nil {
		t.Fatal(err)
	}
	if got := calls.Load(); got != 1 {
		t.Fatalf("valid-lifetime identity exchanges = %d, want 1", got)
	}

	now = now.Add(26 * time.Second)
	if _, err := cache.exchange(context.Background(), "bearer-a", request, exchange); err != nil {
		t.Fatal(err)
	}
	if got := calls.Load(); got != 2 {
		t.Fatalf("near-expiry identity exchanges = %d, want 2", got)
	}
}

func TestIdentityBurstCacheDoesNotCacheFailure(t *testing.T) {
	cache := newIdentityBurstCache()
	now := time.Date(2026, 8, 12, 1, 0, 0, 0, time.UTC)
	cache.now = func() time.Time { return now }
	request := identitysemantic.Request{
		AppInstallationID:     "01900000-0000-7000-8000-000000000001",
		RequestedCapabilities: []string{"runtime.records.query.v1"},
	}
	var calls atomic.Int64
	exchange := func(context.Context, string, identitysemantic.Request) (identitysemantic.Response, error) {
		if calls.Add(1) == 1 {
			return identitysemantic.Response{}, &identityBurstTestError{"temporary failure"}
		}
		return identitysemantic.Response{
			OpaqueDelegation: "header.payload.signature", ExpiresAtUnixSecond: now.Add(time.Minute).Unix(),
		}, nil
	}
	if _, err := cache.exchange(context.Background(), "bearer-a", request, exchange); err == nil {
		t.Fatal("first exchange succeeded, want temporary failure")
	}
	if _, err := cache.exchange(context.Background(), "bearer-a", request, exchange); err != nil {
		t.Fatal(err)
	}
	if got := calls.Load(); got != 2 {
		t.Fatalf("identity exchanges = %d, want failed result retried", got)
	}
}

func TestIdentityBurstCacheSeparatesBearerAndCapabilityAuthority(t *testing.T) {
	cache := newIdentityBurstCache()
	now := time.Date(2026, 8, 12, 1, 0, 0, 0, time.UTC)
	cache.now = func() time.Time { return now }
	var calls atomic.Int64
	exchange := func(_ context.Context, _ string, _ identitysemantic.Request) (identitysemantic.Response, error) {
		calls.Add(1)
		return identitysemantic.Response{
			OpaqueDelegation: "header.payload.signature", ExpiresAtUnixSecond: now.Add(time.Minute).Unix(),
		}, nil
	}
	permission := identitysemantic.Request{
		AppInstallationID:     "01900000-0000-7000-8000-000000000001",
		RequestedCapabilities: []string{"runtime.permissions.effective.read.v1"},
	}
	search := identitysemantic.Request{
		AppInstallationID:     permission.AppInstallationID,
		RequestedCapabilities: []string{"runtime.search.records.v1"},
	}
	for _, attempt := range []struct {
		bearer  string
		request identitysemantic.Request
	}{
		{bearer: "bearer-a", request: permission},
		{bearer: "bearer-b", request: permission},
		{bearer: "bearer-a", request: search},
	} {
		if _, err := cache.exchange(context.Background(), attempt.bearer, attempt.request, exchange); err != nil {
			t.Fatal(err)
		}
	}
	if got := calls.Load(); got != 3 {
		t.Fatalf("identity exchanges = %d, want 3 isolated authorities", got)
	}
}

func TestIdentityExchangeCoordinatorSharesExactAuthorityAcrossClientAdapters(t *testing.T) {
	coordinator := newIdentityExchangeCoordinator(RuntimeClientConfig{
		Target: "passthrough:///runtime-a:9090", WorkloadToken: "workload-a",
	})
	now := time.Date(2026, 8, 12, 2, 0, 0, 0, time.UTC)
	coordinator.bursts.now = func() time.Time { return now }
	request := identitysemantic.Request{
		AppInstallationID:     "01900000-0000-7000-8000-000000000001",
		RequestedCapabilities: []string{"runtime.records.query.v1"},
	}

	var queryAdapterCalls atomic.Int64
	queryExchange := func(_ context.Context, _ string, _ identitysemantic.Request) (identitysemantic.Response, error) {
		queryAdapterCalls.Add(1)
		return identitysemantic.Response{
			OpaqueDelegation: "query-adapter-delegation", ExpiresAtUnixSecond: now.Add(time.Minute).Unix(),
		}, nil
	}
	var secondAdapterCalls atomic.Int64
	secondExchange := func(_ context.Context, _ string, _ identitysemantic.Request) (identitysemantic.Response, error) {
		secondAdapterCalls.Add(1)
		return identitysemantic.Response{
			OpaqueDelegation: "unexpected-second-delegation", ExpiresAtUnixSecond: now.Add(time.Minute).Unix(),
		}, nil
	}

	first, err := coordinator.exchange(context.Background(), "bearer-a", request, queryExchange)
	if err != nil {
		t.Fatal(err)
	}
	second, err := coordinator.exchange(context.Background(), "bearer-a", request, secondExchange)
	if err != nil {
		t.Fatal(err)
	}
	if first.OpaqueDelegation != "query-adapter-delegation" || second.OpaqueDelegation != first.OpaqueDelegation {
		t.Fatalf("shared delegations = %q / %q", first.OpaqueDelegation, second.OpaqueDelegation)
	}
	if got := queryAdapterCalls.Load(); got != 1 {
		t.Fatalf("leader adapter exchanges = %d, want 1", got)
	}
	if got := secondAdapterCalls.Load(); got != 0 {
		t.Fatalf("second adapter exchanges = %d, want 0", got)
	}

	request.RequestedCapabilities = []string{"runtime.records.mutate.v1"}
	if _, err := coordinator.exchange(context.Background(), "bearer-a", request, secondExchange); err != nil {
		t.Fatal(err)
	}
	if got := secondAdapterCalls.Load(); got != 1 {
		t.Fatalf("different-capability exchanges = %d, want 1", got)
	}
}

func TestIdentityBurstKeySeparatesRuntimeAndWorkloadAuthority(t *testing.T) {
	request := identitysemantic.Request{
		AppInstallationID:     "01900000-0000-7000-8000-000000000001",
		RequestedCapabilities: []string{"runtime.records.query.v1"},
	}
	base := RuntimeClientConfig{Target: "passthrough:///runtime-a:9090", WorkloadToken: "workload-a"}
	differentTarget := base
	differentTarget.Target = "passthrough:///runtime-b:9090"
	differentWorkload := base
	differentWorkload.WorkloadToken = "workload-b"

	baseKey := identityBurstKey(runtimeIdentityAuthority(base), "bearer-a", request)
	if targetKey := identityBurstKey(runtimeIdentityAuthority(differentTarget), "bearer-a", request); targetKey == baseKey {
		t.Fatal("different Runtime targets shared one delegation cache key")
	}
	if workloadKey := identityBurstKey(runtimeIdentityAuthority(differentWorkload), "bearer-a", request); workloadKey == baseKey {
		t.Fatal("different workload credentials shared one delegation cache key")
	}
}

type identityBurstTestError struct{ message string }

func (err *identityBurstTestError) Error() string { return err.message }
