package dispatcher

import (
	"fmt"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/desktop/internal/wire/apiclient"
)

func TestExecAuthorizationActivationReservationsBoundConcurrentCleanupOwners(t *testing.T) {
	d := &Dispatcher{}
	d.initServices()

	const extraAttempts = 64
	start := make(chan struct{})
	results := make(chan error, maxExecAuthorizationOwners+extraAttempts)
	var attempts sync.WaitGroup
	for range maxExecAuthorizationOwners + extraAttempts {
		attempts.Add(1)
		go func() {
			defer attempts.Done()
			<-start
			results <- d.reserveExecAuthorizationActivation()
		}()
	}
	close(start)
	attempts.Wait()
	close(results)

	reserved := 0
	for err := range results {
		if err == nil {
			reserved++
			continue
		}
		require.ErrorIs(t, err, errExecAuthorizationOwnerCapacity)
	}
	require.Equal(t, maxExecAuthorizationOwners, reserved)
	d.mu.Lock()
	require.Equal(t, maxExecAuthorizationOwners, d.pendingActivations)
	d.mu.Unlock()
	for range reserved {
		d.releaseExecAuthorizationActivation()
	}
	d.mu.Lock()
	require.Zero(t, d.pendingActivations)
	d.mu.Unlock()
}

func TestExecAuthorizationActivationCountsRetryingCredentialCleanupDebt(t *testing.T) {
	d := &Dispatcher{}
	d.initServices()
	d.authorizations = make(map[string]*execAuthorizationOwner, maxExecAuthorizationOwners)
	for index := range maxExecAuthorizationOwners {
		id := fmt.Sprintf("cleanup-debt-%d", index)
		d.authorizations[id] = &execAuthorizationOwner{id: id}
	}

	require.ErrorIs(t, d.reserveExecAuthorizationActivation(), errExecAuthorizationOwnerCapacity)
	delete(d.authorizations, "cleanup-debt-0")
	require.NoError(t, d.reserveExecAuthorizationActivation())
	d.releaseExecAuthorizationActivation()
}

func TestExecAuthorizationActivationFailureReleasesReservation(t *testing.T) {
	d := &Dispatcher{}
	d.initServices()
	_, err := d.activateExec(
		t.Context(), execTestLaunchContext("server/invalid"), "session-1",
		&execResolved{api: &apiclient.Client{AgentID: "server-agent"}},
	)
	require.ErrorContains(t, err, "path segments")
	d.mu.Lock()
	require.Zero(t, d.pendingActivations)
	d.mu.Unlock()
}

func TestExecCredentialCleanupRetryDelayBacksOffAndCaps(t *testing.T) {
	require.Equal(t, 250*time.Millisecond, execCredentialCleanupRetryDelay(0))
	require.Equal(t, 250*time.Millisecond, execCredentialCleanupRetryDelay(1))
	require.Equal(t, 500*time.Millisecond, execCredentialCleanupRetryDelay(2))
	require.Equal(t, time.Second, execCredentialCleanupRetryDelay(3))
	require.Equal(t, 30*time.Second, execCredentialCleanupRetryDelay(100))
}
