package auth

import (
	"sync"
	"testing"
	"time"
)

func TestStateManager_Store(t *testing.T) {
	sm := &StateManager{
		states: make(map[string]time.Time),
		ttl:    10 * time.Minute,
	}

	sm.Store("test-state")

	sm.mu.RLock()
	_, exists := sm.states["test-state"]
	sm.mu.RUnlock()

	if !exists {
		t.Error("Store() did not store the state")
	}
}

func TestStateManager_Validate(t *testing.T) {
	sm := &StateManager{
		states: make(map[string]time.Time),
		ttl:    10 * time.Minute,
	}

	// Test validating existing state
	sm.Store("valid-state")
	if !sm.Validate("valid-state") {
		t.Error("Validate() returned false for valid state")
	}

	// Test validating non-existent state
	if sm.Validate("non-existent-state") {
		t.Error("Validate() returned true for non-existent state")
	}
}

func TestStateManager_Validate_Expired(t *testing.T) {
	sm := &StateManager{
		states: make(map[string]time.Time),
		ttl:    1 * time.Millisecond,
	}

	// Store a state
	sm.Store("expired-state")

	// Manually set to expired
	sm.mu.Lock()
	sm.states["expired-state"] = time.Now().Add(-2 * time.Hour)
	sm.mu.Unlock()

	// Wait for ttl to pass
	time.Sleep(10 * time.Millisecond)

	// Should return false for expired state
	if sm.Validate("expired-state") {
		t.Error("Validate() should return false for expired state")
	}
}

func TestStateManager_Delete(t *testing.T) {
	sm := &StateManager{
		states: make(map[string]time.Time),
		ttl:    10 * time.Minute,
	}

	sm.Store("state-to-delete")
	sm.Delete("state-to-delete")

	sm.mu.RLock()
	_, exists := sm.states["state-to-delete"]
	sm.mu.RUnlock()

	if exists {
		t.Error("Delete() did not remove the state")
	}
}

func TestStateManager_Cleanup(t *testing.T) {
	sm := &StateManager{
		states: make(map[string]time.Time),
		ttl:    10 * time.Minute,
	}

	// Add some states
	sm.Store("valid-state")

	// Manually add expired states (stored time + ttl should be before now)
	sm.mu.Lock()
	sm.states["expired-state-1"] = time.Now().Add(-20 * time.Minute) // 10 min + 10 min ttl = 20 min ago expired
	sm.states["expired-state-2"] = time.Now().Add(-15 * time.Minute) // 5 min after ttl expired
	sm.mu.Unlock()

	sm.Cleanup()

	sm.mu.RLock()
	defer sm.mu.RUnlock()

	// Valid state should remain
	if _, exists := sm.states["valid-state"]; !exists {
		t.Error("Cleanup() removed valid state")
	}

	// Expired states should be removed
	if _, exists := sm.states["expired-state-1"]; exists {
		t.Error("Cleanup() did not remove expired-state-1")
	}
	if _, exists := sm.states["expired-state-2"]; exists {
		t.Error("Cleanup() did not remove expired-state-2")
	}
}

func TestStateManager_StartCleanupRoutine(t *testing.T) {
	sm := &StateManager{
		states: make(map[string]time.Time),
		ttl:    1 * time.Millisecond,
	}

	stop := make(chan struct{})
	sm.StartCleanupRoutine(stop)

	// Add an expired state
	sm.mu.Lock()
	sm.states["to-cleanup"] = time.Now().Add(-1 * time.Hour)
	sm.mu.Unlock()

	// Wait for cleanup to run (5 minute ticker, but we'll just verify the routine started)
	// We'll stop it immediately for testing
	close(stop)

	// Give goroutine time to exit
	time.Sleep(100 * time.Millisecond)
}

func TestStartCleanupRoutineWithManager(t *testing.T) {
	// Reset for test
	cleanupRoutineManager = nil

	// Start the routine
	StartCleanupRoutineWithManager()

	if cleanupRoutineManager == nil {
		t.Error("StartCleanupRoutineWithManager() did not initialize manager")
	}

	// Starting again should be no-op
	StartCleanupRoutineWithManager()

	// Stop the routine
	StopCleanupRoutine()

	if cleanupRoutineManager != nil {
		t.Error("StopCleanupRoutine() did not clean up manager")
	}
}

func TestStopCleanupRoutine_NilManager(t *testing.T) {
	// Ensure manager is nil
	cleanupRoutineManager = nil

	// Should not panic
	StopCleanupRoutine()
}

func TestGetStateManager(t *testing.T) {
	sm := GetStateManager()

	if sm == nil {
		t.Error("GetStateManager() returned nil")
	}

	// Should return same instance
	sm2 := GetStateManager()
	if sm != sm2 {
		t.Error("GetStateManager() should return same instance")
	}
}

func TestStateManager_ConcurrentAccess(t *testing.T) {
	sm := &StateManager{
		states: make(map[string]time.Time),
		ttl:    10 * time.Minute,
	}

	var wg sync.WaitGroup
	numOps := 100

	// Concurrent stores
	for i := 0; i < numOps; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			sm.Store(string(rune(i)))
		}(i)
	}

	// Concurrent validates
	for i := 0; i < numOps; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			sm.Validate(string(rune(i)))
		}(i)
	}

	wg.Wait()
}
