package token

import (
	"errors"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func TestGenerateAgentTokenForServer_Valid(t *testing.T) {
	mgr := newTestManager(t)

	token, err := mgr.GenerateAgentTokenForServer("agent-1", "")
	if err != nil {
		t.Fatalf("GenerateAgentTokenForServer() error = %v", err)
	}
	if token == "" {
		t.Fatal("GenerateAgentTokenForServer() returned empty token")
	}
	if len(token) < len(agentTokenPrefix)+2 {
		t.Errorf("token too short: %q", token)
	}
}

func TestGenerateAgentTokenForServer_EmptyAgentID(t *testing.T) {
	mgr := newTestManager(t)

	_, err := mgr.GenerateAgentTokenForServer("", "")
	if err == nil {
		t.Error("GenerateAgentTokenForServer() should error for empty agentID")
	}
	_, err = mgr.GenerateAgentTokenForServer("  ", "")
	if err == nil {
		t.Error("GenerateAgentTokenForServer() should error for whitespace agentID")
	}
}

func TestValidateAgentTokenInfo_Valid(t *testing.T) {
	mgr := newTestManager(t)

	token, err := mgr.GenerateAgentTokenForServer("agent-1", "")
	if err != nil {
		t.Fatalf("GenerateAgentTokenForServer() error = %v", err)
	}

	if _, err := mgr.ValidateAgentTokenInfo("agent-1", token); err != nil {
		t.Errorf("ValidateAgentTokenInfo() error = %v", err)
	}
}

func TestValidateAgentTokenForServer_Valid(t *testing.T) {
	mgr := newTestManager(t)

	token, err := mgr.GenerateAgentTokenForServer("agent-1", "server-a")
	if err != nil {
		t.Fatalf("GenerateAgentTokenForServer() error = %v", err)
	}

	if _, err := mgr.ValidateAgentTokenForServerInfo("agent-1", "server-a", token); err != nil {
		t.Errorf("ValidateAgentTokenForServerInfo() error = %v", err)
	}
	if _, err := mgr.ValidateAgentTokenInfo("agent-1", token); err != nil {
		t.Errorf("ValidateAgentTokenInfo() for daemon route error = %v", err)
	}
	if _, err := mgr.ValidateUnscopedAgentTokenInfo("agent-1", token); err != nil {
		t.Errorf("ValidateUnscopedAgentTokenInfo() error = %v", err)
	}
}

func TestTokenAnalysisAgentTokenScope(t *testing.T) {
	mgr := newTestManager(t)

	tokenValue, err := mgr.GenerateTokenAnalysisAgentToken("agent-1")
	require.NoError(t, err)

	info, err := mgr.ValidateTokenAnalysisAgentTokenInfo("agent-1", tokenValue)
	require.NoError(t, err)
	require.Equal(t, AgentTokenScopeTokenAnalysis, info.Scope)
	_, err = mgr.ValidateAgentTokenInfo("agent-1", tokenValue)
	require.ErrorIs(t, err, ErrAgentTokenScopeMismatch)
	_, err = mgr.ValidateAgentTokenForServerInfo("agent-1", "server-a", tokenValue)
	require.ErrorIs(t, err, ErrAgentTokenServerMismatch)
	_, err = mgr.ValidateUnscopedAgentTokenInfo("agent-1", tokenValue)
	require.ErrorIs(t, err, ErrAgentTokenScopeMismatch)
	_, err = mgr.ValidateTokenAnalysisAgentTokenInfo("agent-2", tokenValue)
	require.ErrorIs(t, err, ErrAgentMismatch)

	require.True(t, mgr.RevokeAgentToken(tokenValue))
	_, err = mgr.ValidateTokenAnalysisAgentTokenInfo("agent-1", tokenValue)
	require.ErrorIs(t, err, ErrAgentTokenRevoked)
}

func TestValidateAgentTokenForServer_ServerMismatch(t *testing.T) {
	mgr := newTestManager(t)

	token, err := mgr.GenerateAgentTokenForServer("agent-1", "server-a")
	if err != nil {
		t.Fatalf("GenerateAgentTokenForServer() error = %v", err)
	}

	_, err = mgr.ValidateAgentTokenForServerInfo("agent-1", "server-b", token)
	if !errors.Is(err, ErrAgentTokenServerMismatch) {
		t.Errorf("ValidateAgentTokenForServerInfo() error = %v, want %v", err, ErrAgentTokenServerMismatch)
	}
}

func TestValidateAgentTokenForServer_UnboundTokenRejected(t *testing.T) {
	mgr := newTestManager(t)

	token, err := mgr.GenerateAgentTokenForServer("agent-1", "")
	if err != nil {
		t.Fatalf("GenerateAgentTokenForServer() error = %v", err)
	}

	_, err = mgr.ValidateAgentTokenForServerInfo("agent-1", "server-a", token)
	if !errors.Is(err, ErrAgentTokenServerMismatch) {
		t.Errorf("ValidateAgentTokenForServerInfo() error = %v, want %v", err, ErrAgentTokenServerMismatch)
	}
}

func TestValidateAgentTokenInfo_Revoked(t *testing.T) {
	mgr := newTestManager(t)

	token, err := mgr.GenerateAgentTokenForServer("agent-1", "server-a")
	if err != nil {
		t.Fatalf("GenerateAgentTokenForServer() error = %v", err)
	}
	if !mgr.RevokeAgentToken(token) {
		t.Fatal("RevokeAgentToken() = false, want true")
	}

	_, err = mgr.ValidateAgentTokenForServerInfo("agent-1", "server-a", token)
	if !errors.Is(err, ErrAgentTokenRevoked) {
		t.Errorf("ValidateAgentTokenForServerInfo() error = %v, want %v", err, ErrAgentTokenRevoked)
	}
}

func TestAgentTokenLimitEvictsRevokedBeforeActiveTokens(t *testing.T) {
	mgr := newTestManager(t)
	base := time.Date(2026, 5, 1, 12, 0, 0, 0, time.UTC)
	now := base
	mgr.now = func() time.Time { return now }

	active, err := mgr.GenerateAgentTokenForServer("agent-active", "server-a")
	if err != nil {
		t.Fatalf("GenerateAgentTokenForServer() active error = %v", err)
	}

	for i := 0; i < maxAgentTokens+1; i++ {
		now = base.Add(time.Duration(i+1) * time.Second)
		tok, err := mgr.GenerateAgentTokenForServer("agent-churn", "server-a")
		if err != nil {
			t.Fatalf("GenerateAgentTokenForServer() churn %d error = %v", i, err)
		}
		if !mgr.RevokeAgentToken(tok) {
			t.Fatalf("RevokeAgentToken() churn %d = false, want true", i)
		}
	}

	now = base.Add(time.Hour)
	if _, err := mgr.ValidateAgentTokenForServerInfo("agent-active", "server-a", active); err != nil {
		t.Fatalf("ValidateAgentTokenForServerInfo() active token error = %v", err)
	}
	mgr.mu.RLock()
	got := len(mgr.agentTokens)
	mgr.mu.RUnlock()
	if got > maxAgentTokens {
		t.Fatalf("agent token count = %d, want <= %d", got, maxAgentTokens)
	}
}

func TestValidateAgentTokenInfo_Expired(t *testing.T) {
	mgr := newTestManager(t)
	base := time.Date(2026, 5, 1, 12, 0, 0, 0, time.UTC)
	now := base
	mgr.now = func() time.Time { return now }

	token, err := mgr.GenerateAgentTokenForServer("agent-1", "server-a")
	if err != nil {
		t.Fatalf("GenerateAgentTokenForServer() error = %v", err)
	}

	now = base.Add(agentTokenMaxDuration + time.Nanosecond)
	_, err = mgr.ValidateAgentTokenForServerInfo("agent-1", "server-a", token)
	if !errors.Is(err, ErrExpired) {
		t.Errorf("ValidateAgentTokenForServerInfo() error = %v, want %v", err, ErrExpired)
	}
}

func TestValidateAgentTokenInfo_AgentMismatch(t *testing.T) {
	mgr := newTestManager(t)

	token, err := mgr.GenerateAgentTokenForServer("agent-1", "")
	if err != nil {
		t.Fatalf("GenerateAgentTokenForServer() error = %v", err)
	}

	if _, err := mgr.ValidateAgentTokenInfo("agent-2", token); err != ErrAgentMismatch {
		t.Errorf("ValidateAgentTokenInfo() error = %v, want %v", err, ErrAgentMismatch)
	}
}

func TestValidateAgentTokenInfo_DifferentManager(t *testing.T) {
	mgr1 := newTestManager(t)
	mgr2 := newTestManager(t)

	token, err := mgr1.GenerateAgentTokenForServer("agent-1", "")
	if err != nil {
		t.Fatalf("GenerateAgentTokenForServer() error = %v", err)
	}

	if _, err := mgr2.ValidateAgentTokenInfo("agent-1", token); err != ErrInvalidSignature {
		t.Errorf("ValidateAgentTokenInfo() error = %v, want %v", err, ErrInvalidSignature)
	}
}

func TestValidateAgentTokenInfo_InvalidFormat(t *testing.T) {
	mgr := newTestManager(t)

	tests := []struct {
		name    string
		agentID string
		token   string
		wantErr error
	}{
		{"empty token", "agent-1", "", ErrInvalidFormat},
		{"missing prefix", "agent-1", "abc.123.456", ErrInvalidFormat},
		{"no dots", "agent-1", agentTokenPrefix + "nodots", ErrInvalidFormat},
		{"invalid base64", "agent-1", agentTokenPrefix + ".!!invalid!!.abc", ErrInvalidFormat},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := mgr.ValidateAgentTokenInfo(tt.agentID, tt.token)
			if err == nil {
				t.Error("expected error")
			}
		})
	}
}

func TestValidateAgentTokenInfo_EmptyAgentID(t *testing.T) {
	mgr := newTestManager(t)

	token, _ := mgr.GenerateAgentTokenForServer("agent-1", "")
	_, err := mgr.ValidateAgentTokenInfo("", token)
	if err == nil {
		t.Error("ValidateAgentTokenInfo should error for empty agentID")
	}

	_, err = mgr.ValidateAgentTokenInfo("  ", token)
	if err == nil {
		t.Error("ValidateAgentTokenInfo should error for whitespace agentID")
	}
}

func TestGenerateAgentTokenForServer_Uninitialized(t *testing.T) {
	mgr := &KeyManager{}
	_, err := mgr.GenerateAgentTokenForServer("agent-1", "")
	if err == nil {
		t.Error("GenerateAgentTokenForServer should error for uninitialized manager")
	}
}

func TestValidateAgentTokenInfo_Uninitialized(t *testing.T) {
	mgr := &KeyManager{}
	_, err := mgr.ValidateAgentTokenInfo("agent-1", "at-dev.abc.def")
	if err == nil {
		t.Error("ValidateAgentTokenInfo should error for uninitialized manager")
	}
}
