// Package state provides state management for AutoSSO.
// This file contains tests for the file-based storage backend.
//
// Requirements: 4.5, 4.6
package state

import (
	"context"
	"path/filepath"
	"sync"
	"testing"
	"time"

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

// TestNewFileStore tests the creation of a new file store
func TestNewFileStore(t *testing.T) {
	t.Run("creates store with valid config", func(t *testing.T) {
		tmpDir := t.TempDir()
		cfg := &FileStoreConfig{
			DataDir:     tmpDir,
			LockTimeout: 5 * time.Second,
		}

		store, err := NewFileStore(cfg)
		require.NoError(t, err)
		require.NotNil(t, store)
		defer store.Close()

		// Verify data files were created
		assert.FileExists(t, filepath.Join(tmpDir, approvalInstancesFile))
		assert.FileExists(t, filepath.Join(tmpDir, pendingRemovalsFile))
		assert.FileExists(t, filepath.Join(tmpDir, processedInstancesFile))
	})

	t.Run("returns error with nil config", func(t *testing.T) {
		store, err := NewFileStore(nil)
		assert.Error(t, err)
		assert.Nil(t, store)
		assert.Contains(t, err.Error(), "configuration is required")
	})

	t.Run("returns error with empty data dir", func(t *testing.T) {
		cfg := &FileStoreConfig{
			DataDir: "",
		}
		store, err := NewFileStore(cfg)
		assert.Error(t, err)
		assert.Nil(t, store)
		assert.Contains(t, err.Error(), "data directory is required")
	})

	t.Run("uses default lock timeout", func(t *testing.T) {
		tmpDir := t.TempDir()
		cfg := &FileStoreConfig{
			DataDir: tmpDir,
		}

		store, err := NewFileStore(cfg)
		require.NoError(t, err)
		require.NotNil(t, store)
		defer store.Close()

		assert.Equal(t, defaultLockTimeout, store.lockTimeout)
	})
}

// TestFileStoreSaveAndGetInstance tests saving and retrieving instances
func TestFileStoreSaveAndGetInstance(t *testing.T) {
	store := createTestFileStore(t)
	defer store.Close()
	ctx := context.Background()

	t.Run("saves and retrieves instance", func(t *testing.T) {
		instance := &ApprovalInstance{
			ID:             "test-instance-1",
			Email:          "user@example.com",
			AccountID:      "123456789",
			PermissionSets: []string{"ViewOnly", "Admin"},
			StartTime:      time.Now(),
			EndTime:        time.Now().Add(24 * time.Hour),
			Status:         StatusPending,
			CloudType:      CloudTypeAWS,
		}

		err := store.SaveInstance(ctx, instance)
		require.NoError(t, err)

		retrieved, err := store.GetInstance(ctx, instance.ID)
		require.NoError(t, err)
		require.NotNil(t, retrieved)

		assert.Equal(t, instance.ID, retrieved.ID)
		assert.Equal(t, instance.Email, retrieved.Email)
		assert.Equal(t, instance.AccountID, retrieved.AccountID)
		assert.Equal(t, instance.PermissionSets, retrieved.PermissionSets)
		assert.Equal(t, instance.Status, retrieved.Status)
		assert.Equal(t, instance.CloudType, retrieved.CloudType)
		assert.False(t, retrieved.CreatedAt.IsZero())
		assert.False(t, retrieved.UpdatedAt.IsZero())
	})

	t.Run("updates existing instance", func(t *testing.T) {
		instance := &ApprovalInstance{
			ID:             "test-instance-update",
			Email:          "user@example.com",
			AccountID:      "123456789",
			PermissionSets: []string{"ViewOnly"},
			StartTime:      time.Now(),
			EndTime:        time.Now().Add(24 * time.Hour),
			Status:         StatusPending,
			CloudType:      CloudTypeAWS,
		}

		err := store.SaveInstance(ctx, instance)
		require.NoError(t, err)

		// Update the instance
		instance.Status = StatusApproved
		instance.PermissionSets = []string{"ViewOnly", "Admin"}
		err = store.SaveInstance(ctx, instance)
		require.NoError(t, err)

		retrieved, err := store.GetInstance(ctx, instance.ID)
		require.NoError(t, err)
		require.NotNil(t, retrieved)

		assert.Equal(t, StatusApproved, retrieved.Status)
		assert.Equal(t, []string{"ViewOnly", "Admin"}, retrieved.PermissionSets)
	})

	t.Run("returns nil for non-existent instance", func(t *testing.T) {
		retrieved, err := store.GetInstance(ctx, "non-existent-id")
		require.NoError(t, err)
		assert.Nil(t, retrieved)
	})

	t.Run("returns error for nil instance", func(t *testing.T) {
		err := store.SaveInstance(ctx, nil)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "instance is required")
	})

	t.Run("returns error for empty instance ID", func(t *testing.T) {
		_, err := store.GetInstance(ctx, "")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "instance ID is required")
	})
}

// TestFileStoreListInstances tests listing instances with filters
func TestFileStoreListInstances(t *testing.T) {
	store := createTestFileStore(t)
	defer store.Close()
	ctx := context.Background()

	// Create test instances
	instances := []*ApprovalInstance{
		{
			ID:             "list-test-1",
			Email:          "user1@example.com",
			AccountID:      "account-1",
			PermissionSets: []string{"ViewOnly"},
			StartTime:      time.Now(),
			EndTime:        time.Now().Add(24 * time.Hour),
			Status:         StatusPending,
			CloudType:      CloudTypeAWS,
		},
		{
			ID:             "list-test-2",
			Email:          "user2@example.com",
			AccountID:      "account-2",
			PermissionSets: []string{"Admin"},
			StartTime:      time.Now(),
			EndTime:        time.Now().Add(48 * time.Hour),
			Status:         StatusApproved,
			CloudType:      CloudTypeGCP,
		},
		{
			ID:             "list-test-3",
			Email:          "user1@example.com",
			AccountID:      "account-1",
			PermissionSets: []string{"Admin"},
			StartTime:      time.Now(),
			EndTime:        time.Now().Add(72 * time.Hour),
			Status:         StatusApproved,
			CloudType:      CloudTypeAWS,
		},
	}

	for _, instance := range instances {
		err := store.SaveInstance(ctx, instance)
		require.NoError(t, err)
	}

	t.Run("lists all instances without filter", func(t *testing.T) {
		results, err := store.ListInstances(ctx, nil)
		require.NoError(t, err)
		assert.GreaterOrEqual(t, len(results), 3)
	})

	t.Run("filters by status", func(t *testing.T) {
		filter := &InstanceFilter{Status: StatusApproved}
		results, err := store.ListInstances(ctx, filter)
		require.NoError(t, err)
		for _, r := range results {
			assert.Equal(t, StatusApproved, r.Status)
		}
	})

	t.Run("filters by cloud type", func(t *testing.T) {
		filter := &InstanceFilter{CloudType: CloudTypeAWS}
		results, err := store.ListInstances(ctx, filter)
		require.NoError(t, err)
		for _, r := range results {
			assert.Equal(t, CloudTypeAWS, r.CloudType)
		}
	})

	t.Run("filters by email", func(t *testing.T) {
		filter := &InstanceFilter{Email: "user1@example.com"}
		results, err := store.ListInstances(ctx, filter)
		require.NoError(t, err)
		for _, r := range results {
			assert.Equal(t, "user1@example.com", r.Email)
		}
	})

	t.Run("applies pagination", func(t *testing.T) {
		filter := &InstanceFilter{Limit: 2}
		results, err := store.ListInstances(ctx, filter)
		require.NoError(t, err)
		assert.LessOrEqual(t, len(results), 2)
	})
}

// TestFileStoreDeleteInstance tests deleting instances
func TestFileStoreDeleteInstance(t *testing.T) {
	store := createTestFileStore(t)
	defer store.Close()
	ctx := context.Background()

	t.Run("deletes existing instance", func(t *testing.T) {
		instance := &ApprovalInstance{
			ID:             "delete-test-1",
			Email:          "user@example.com",
			AccountID:      "123456789",
			PermissionSets: []string{"ViewOnly"},
			StartTime:      time.Now(),
			EndTime:        time.Now().Add(24 * time.Hour),
			Status:         StatusPending,
			CloudType:      CloudTypeAWS,
		}

		err := store.SaveInstance(ctx, instance)
		require.NoError(t, err)

		err = store.DeleteInstance(ctx, instance.ID)
		require.NoError(t, err)

		retrieved, err := store.GetInstance(ctx, instance.ID)
		require.NoError(t, err)
		assert.Nil(t, retrieved)
	})

	t.Run("no error for non-existent instance", func(t *testing.T) {
		err := store.DeleteInstance(ctx, "non-existent-id")
		assert.NoError(t, err)
	})

	t.Run("returns error for empty instance ID", func(t *testing.T) {
		err := store.DeleteInstance(ctx, "")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "instance ID is required")
	})
}

// TestFileStorePendingRemovals tests pending removal operations
func TestFileStorePendingRemovals(t *testing.T) {
	store := createTestFileStore(t)
	defer store.Close()
	ctx := context.Background()

	t.Run("saves and retrieves pending removal", func(t *testing.T) {
		removal := &PendingRemoval{
			AccountID:     "account-1",
			Email:         "user@example.com",
			PermissionSet: "ViewOnly",
			CloudType:     CloudTypeAWS,
			ExpiredAt:     time.Now(),
			ScheduledAt:   time.Now().Add(72 * time.Hour),
		}

		err := store.SavePendingRemoval(ctx, removal)
		require.NoError(t, err)
		assert.Greater(t, removal.ID, int64(0))

		removals, err := store.GetPendingRemovals(ctx)
		require.NoError(t, err)
		assert.NotEmpty(t, removals)

		found := false
		for _, r := range removals {
			if r.AccountID == removal.AccountID &&
				r.Email == removal.Email &&
				r.PermissionSet == removal.PermissionSet {
				found = true
				assert.Equal(t, removal.CloudType, r.CloudType)
				break
			}
		}
		assert.True(t, found, "saved removal not found in list")
	})

	t.Run("updates existing removal with same key", func(t *testing.T) {
		removal := &PendingRemoval{
			AccountID:     "account-update",
			Email:         "user@example.com",
			PermissionSet: "Admin",
			CloudType:     CloudTypeGCP,
			ExpiredAt:     time.Now(),
			ScheduledAt:   time.Now().Add(72 * time.Hour),
		}

		err := store.SavePendingRemoval(ctx, removal)
		require.NoError(t, err)
		originalID := removal.ID

		// Update with same key
		removal.ScheduledAt = time.Now().Add(96 * time.Hour)
		err = store.SavePendingRemoval(ctx, removal)
		require.NoError(t, err)

		// ID should remain the same
		assert.Equal(t, originalID, removal.ID)
	})

	t.Run("deletes pending removal", func(t *testing.T) {
		removal := &PendingRemoval{
			AccountID:     "account-delete",
			Email:         "user@example.com",
			PermissionSet: "Delete",
			CloudType:     CloudTypeAzure,
			ExpiredAt:     time.Now(),
			ScheduledAt:   time.Now().Add(72 * time.Hour),
		}

		err := store.SavePendingRemoval(ctx, removal)
		require.NoError(t, err)

		err = store.DeletePendingRemoval(ctx, removal.ID)
		require.NoError(t, err)

		removals, err := store.GetPendingRemovals(ctx)
		require.NoError(t, err)

		for _, r := range removals {
			assert.NotEqual(t, removal.ID, r.ID)
		}
	})

	t.Run("returns error for nil removal", func(t *testing.T) {
		err := store.SavePendingRemoval(ctx, nil)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "removal is required")
	})

	t.Run("returns error for invalid ID", func(t *testing.T) {
		err := store.DeletePendingRemoval(ctx, 0)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "valid ID is required")
	})
}

// TestFileStoreProcessedInstances tests processed instance tracking
func TestFileStoreProcessedInstances(t *testing.T) {
	store := createTestFileStore(t)
	defer store.Close()
	ctx := context.Background()

	t.Run("saves and checks deleted instance", func(t *testing.T) {
		instanceID := "processed-test-1"

		// Initially not deleted
		deleted, err := store.IsInstanceDeleted(ctx, instanceID)
		require.NoError(t, err)
		assert.False(t, deleted)

		// Mark as deleted
		err = store.SaveDeletedInstance(ctx, instanceID)
		require.NoError(t, err)

		// Now should be deleted
		deleted, err = store.IsInstanceDeleted(ctx, instanceID)
		require.NoError(t, err)
		assert.True(t, deleted)
	})

	t.Run("returns error for empty instance ID on save", func(t *testing.T) {
		err := store.SaveDeletedInstance(ctx, "")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "instance ID is required")
	})

	t.Run("returns error for empty instance ID on check", func(t *testing.T) {
		_, err := store.IsInstanceDeleted(ctx, "")
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "instance ID is required")
	})
}

// TestFileStoreGetReadyRemovals tests getting ready removals
func TestFileStoreGetReadyRemovals(t *testing.T) {
	store := createTestFileStore(t)
	defer store.Close()
	ctx := context.Background()

	// Create removals with different scheduled times
	pastRemoval := &PendingRemoval{
		AccountID:     "account-past",
		Email:         "user@example.com",
		PermissionSet: "Past",
		CloudType:     CloudTypeAWS,
		ExpiredAt:     time.Now().Add(-72 * time.Hour),
		ScheduledAt:   time.Now().Add(-24 * time.Hour), // In the past
	}

	futureRemoval := &PendingRemoval{
		AccountID:     "account-future",
		Email:         "user@example.com",
		PermissionSet: "Future",
		CloudType:     CloudTypeGCP,
		ExpiredAt:     time.Now(),
		ScheduledAt:   time.Now().Add(72 * time.Hour), // In the future
	}

	err := store.SavePendingRemoval(ctx, pastRemoval)
	require.NoError(t, err)

	err = store.SavePendingRemoval(ctx, futureRemoval)
	require.NoError(t, err)

	ready, err := store.GetReadyRemovals(ctx)
	require.NoError(t, err)

	// Should only include past removal
	foundPast := false
	foundFuture := false
	for _, r := range ready {
		if r.AccountID == "account-past" {
			foundPast = true
		}
		if r.AccountID == "account-future" {
			foundFuture = true
		}
	}

	assert.True(t, foundPast, "past removal should be ready")
	assert.False(t, foundFuture, "future removal should not be ready")
}

// TestFileStoreConcurrentAccess tests concurrent access safety
// Requirements: 4.6
func TestFileStoreConcurrentAccess(t *testing.T) {
	store := createTestFileStore(t)
	defer store.Close()
	ctx := context.Background()

	t.Run("concurrent saves don't corrupt data", func(t *testing.T) {
		var wg sync.WaitGroup
		numGoroutines := 10
		numOperations := 5

		for i := 0; i < numGoroutines; i++ {
			wg.Add(1)
			go func(goroutineID int) {
				defer wg.Done()
				for j := 0; j < numOperations; j++ {
					instance := &ApprovalInstance{
						ID:             "concurrent-" + string(rune('A'+goroutineID)) + "-" + string(rune('0'+j)),
						Email:          "user@example.com",
						AccountID:      "123456789",
						PermissionSets: []string{"ViewOnly"},
						StartTime:      time.Now(),
						EndTime:        time.Now().Add(24 * time.Hour),
						Status:         StatusPending,
						CloudType:      CloudTypeAWS,
					}
					err := store.SaveInstance(ctx, instance)
					assert.NoError(t, err)
				}
			}(i)
		}

		wg.Wait()

		// Verify all instances were saved
		instances, err := store.ListInstances(ctx, nil)
		require.NoError(t, err)

		// Count concurrent instances
		count := 0
		for _, inst := range instances {
			if len(inst.ID) > 11 && inst.ID[:11] == "concurrent-" {
				count++
			}
		}
		assert.Equal(t, numGoroutines*numOperations, count)
	})

	t.Run("concurrent reads and writes", func(t *testing.T) {
		// First save an instance
		instance := &ApprovalInstance{
			ID:             "rw-test",
			Email:          "user@example.com",
			AccountID:      "123456789",
			PermissionSets: []string{"ViewOnly"},
			StartTime:      time.Now(),
			EndTime:        time.Now().Add(24 * time.Hour),
			Status:         StatusPending,
			CloudType:      CloudTypeAWS,
		}
		err := store.SaveInstance(ctx, instance)
		require.NoError(t, err)

		var wg sync.WaitGroup
		numReaders := 5
		numWriters := 3

		// Start readers
		for i := 0; i < numReaders; i++ {
			wg.Add(1)
			go func() {
				defer wg.Done()
				for j := 0; j < 10; j++ {
					_, err := store.GetInstance(ctx, "rw-test")
					assert.NoError(t, err)
				}
			}()
		}

		// Start writers
		for i := 0; i < numWriters; i++ {
			wg.Add(1)
			go func(writerID int) {
				defer wg.Done()
				for j := 0; j < 5; j++ {
					instance := &ApprovalInstance{
						ID:             "rw-test",
						Email:          "user@example.com",
						AccountID:      "123456789",
						PermissionSets: []string{"ViewOnly", "Admin"},
						StartTime:      time.Now(),
						EndTime:        time.Now().Add(24 * time.Hour),
						Status:         StatusApproved,
						CloudType:      CloudTypeAWS,
					}
					err := store.SaveInstance(ctx, instance)
					assert.NoError(t, err)
				}
			}(i)
		}

		wg.Wait()
	})
}

// TestFileStoreContextCancellation tests context cancellation handling
func TestFileStoreContextCancellation(t *testing.T) {
	store := createTestFileStore(t)
	defer store.Close()

	t.Run("respects context cancellation", func(t *testing.T) {
		ctx, cancel := context.WithCancel(context.Background())
		cancel() // Cancel immediately

		instance := &ApprovalInstance{
			ID:             "cancel-test",
			Email:          "user@example.com",
			AccountID:      "123456789",
			PermissionSets: []string{"ViewOnly"},
			StartTime:      time.Now(),
			EndTime:        time.Now().Add(24 * time.Hour),
			Status:         StatusPending,
			CloudType:      CloudTypeAWS,
		}

		err := store.SaveInstance(ctx, instance)
		assert.Error(t, err)
	})
}

// TestFileStoreClose tests the Close method
func TestFileStoreClose(t *testing.T) {
	store := createTestFileStore(t)

	err := store.Close()
	assert.NoError(t, err)

	// Closing again should also be fine
	err = store.Close()
	assert.NoError(t, err)
}

// TestFileStoreImplementsInterface verifies FileStore implements Store interface
func TestFileStoreImplementsInterface(t *testing.T) {
	var _ Store = (*FileStore)(nil)
}

// createTestFileStore creates a file store for testing
func createTestFileStore(t *testing.T) *FileStore {
	t.Helper()
	tmpDir := t.TempDir()
	cfg := &FileStoreConfig{
		DataDir:     tmpDir,
		LockTimeout: 5 * time.Second,
	}

	store, err := NewFileStore(cfg)
	require.NoError(t, err)
	return store
}
