// Package state provides state management for AutoSSO.
// This file contains unit tests for the MySQL storage backend.
package state

import (
	"context"
	"encoding/json"
	"errors"
	"strings"
	"testing"
	"time"

	"AutoSSO/internal/config"
)

type fakePinger struct {
	ctx context.Context
}

func (f *fakePinger) PingContext(ctx context.Context) error {
	f.ctx = ctx
	return nil
}

func TestBuildMySQLDSN_IncludesTimeouts(t *testing.T) {
	cfg := &config.DatabaseConfig{
		User:     "user",
		Password: "pass",
		Host:     "localhost",
		Port:     3306,
		Database: "autosso",
	}

	dsn := buildMySQLDSN(cfg)
	if !strings.Contains(dsn, "timeout=") {
		t.Fatalf("expected DSN to include timeout, got: %s", dsn)
	}
	if !strings.Contains(dsn, "readTimeout=") {
		t.Fatalf("expected DSN to include readTimeout, got: %s", dsn)
	}
	if !strings.Contains(dsn, "writeTimeout=") {
		t.Fatalf("expected DSN to include writeTimeout, got: %s", dsn)
	}
}

func TestPingWithTimeout_SetsDeadline(t *testing.T) {
	p := &fakePinger{}
	if err := pingWithTimeout(p, 50*time.Millisecond); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if p.ctx == nil {
		t.Fatal("expected PingContext to be called with a context")
	}
	if _, ok := p.ctx.Deadline(); !ok {
		t.Fatal("expected context deadline to be set")
	}
}

func TestEnsureSchemaWithTimeout_SetsDeadline(t *testing.T) {
	called := false
	hasDeadline := false
	err := ensureSchemaWithTimeout(50*time.Millisecond, func(ctx context.Context) error {
		called = true
		_, hasDeadline = ctx.Deadline()
		return nil
	})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !called {
		t.Fatal("expected ensureSchema to be called")
	}
	if !hasDeadline {
		t.Fatal("expected context deadline to be set")
	}
}

// TestNewMySQLStore_NilConfig tests that NewMySQLStore returns an error when config is nil.
func TestNewMySQLStore_NilConfig(t *testing.T) {
	store, err := NewMySQLStore(nil)
	if err == nil {
		t.Error("expected error when config is nil, got nil")
	}
	if store != nil {
		t.Error("expected store to be nil when config is nil")
	}
	if err.Error() != "database configuration is required" {
		t.Errorf("unexpected error message: %s", err.Error())
	}
}

// TestMySQLStore_SaveInstance_NilInstance tests that SaveInstance returns an error when instance is nil.
func TestMySQLStore_SaveInstance_NilInstance(t *testing.T) {
	// Create a mock store for testing validation logic
	store := &MySQLStore{}

	err := store.SaveInstance(context.Background(), nil)
	if err == nil {
		t.Error("expected error when instance is nil, got nil")
	}
	if err.Error() != "instance is required" {
		t.Errorf("unexpected error message: %s", err.Error())
	}
}

// TestMySQLStore_GetInstance_EmptyID tests that GetInstance returns an error when instanceID is empty.
func TestMySQLStore_GetInstance_EmptyID(t *testing.T) {
	store := &MySQLStore{}

	instance, err := store.GetInstance(context.Background(), "")
	if err == nil {
		t.Error("expected error when instanceID is empty, got nil")
	}
	if instance != nil {
		t.Error("expected instance to be nil when instanceID is empty")
	}
	if err.Error() != "instance ID is required" {
		t.Errorf("unexpected error message: %s", err.Error())
	}
}

// TestMySQLStore_DeleteInstance_EmptyID tests that DeleteInstance returns an error when instanceID is empty.
func TestMySQLStore_DeleteInstance_EmptyID(t *testing.T) {
	store := &MySQLStore{}

	err := store.DeleteInstance(context.Background(), "")
	if err == nil {
		t.Error("expected error when instanceID is empty, got nil")
	}
	if err.Error() != "instance ID is required" {
		t.Errorf("unexpected error message: %s", err.Error())
	}
}

// TestMySQLStore_SavePendingRemoval_NilRemoval tests that SavePendingRemoval returns an error when removal is nil.
func TestMySQLStore_SavePendingRemoval_NilRemoval(t *testing.T) {
	store := &MySQLStore{}

	err := store.SavePendingRemoval(context.Background(), nil)
	if err == nil {
		t.Error("expected error when removal is nil, got nil")
	}
	if err.Error() != "removal is required" {
		t.Errorf("unexpected error message: %s", err.Error())
	}
}

// TestMySQLStore_DeletePendingRemoval_InvalidID tests that DeletePendingRemoval returns an error when ID is invalid.
func TestMySQLStore_DeletePendingRemoval_InvalidID(t *testing.T) {
	store := &MySQLStore{}

	testCases := []struct {
		name string
		id   int64
	}{
		{"zero ID", 0},
		{"negative ID", -1},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			err := store.DeletePendingRemoval(context.Background(), tc.id)
			if err == nil {
				t.Error("expected error when ID is invalid, got nil")
			}
			if err.Error() != "valid ID is required" {
				t.Errorf("unexpected error message: %s", err.Error())
			}
		})
	}
}

// TestMySQLStore_SaveDeletedInstance_EmptyID tests that SaveDeletedInstance returns an error when instanceID is empty.
func TestMySQLStore_SaveDeletedInstance_EmptyID(t *testing.T) {
	store := &MySQLStore{}

	err := store.SaveDeletedInstance(context.Background(), "")
	if err == nil {
		t.Error("expected error when instanceID is empty, got nil")
	}
	if err.Error() != "instance ID is required" {
		t.Errorf("unexpected error message: %s", err.Error())
	}
}

// TestMySQLStore_IsInstanceDeleted_EmptyID tests that IsInstanceDeleted returns an error when instanceID is empty.
func TestMySQLStore_IsInstanceDeleted_EmptyID(t *testing.T) {
	store := &MySQLStore{}

	deleted, err := store.IsInstanceDeleted(context.Background(), "")
	if err == nil {
		t.Error("expected error when instanceID is empty, got nil")
	}
	if deleted {
		t.Error("expected deleted to be false when instanceID is empty")
	}
	if err.Error() != "instance ID is required" {
		t.Errorf("unexpected error message: %s", err.Error())
	}
}

// TestMySQLStore_UpdateInstanceStatus_EmptyID tests that UpdateInstanceStatus returns an error when instanceID is empty.
func TestMySQLStore_UpdateInstanceStatus_EmptyID(t *testing.T) {
	store := &MySQLStore{}

	err := store.UpdateInstanceStatus(context.Background(), "", StatusApproved)
	if err == nil {
		t.Error("expected error when instanceID is empty, got nil")
	}
	if err.Error() != "instance ID is required" {
		t.Errorf("unexpected error message: %s", err.Error())
	}
}

// TestMySQLStore_UpdateInstanceStatus_InvalidStatus tests that UpdateInstanceStatus returns an error when status is invalid.
func TestMySQLStore_UpdateInstanceStatus_InvalidStatus(t *testing.T) {
	store := &MySQLStore{}

	err := store.UpdateInstanceStatus(context.Background(), "test-id", "invalid-status")
	if err == nil {
		t.Error("expected error when status is invalid, got nil")
	}
	expectedErr := "invalid status: invalid-status"
	if err.Error() != expectedErr {
		t.Errorf("unexpected error message: got %s, want %s", err.Error(), expectedErr)
	}
}

// TestMySQLStore_Close_NilDB tests that Close handles nil db gracefully.
func TestMySQLStore_Close_NilDB(t *testing.T) {
	store := &MySQLStore{db: nil}

	err := store.Close()
	if err != nil {
		t.Errorf("expected no error when db is nil, got: %v", err)
	}
}

// TestPermissionSetsJSONSerialization tests that permission sets are correctly serialized to JSON.
func TestPermissionSetsJSONSerialization(t *testing.T) {
	testCases := []struct {
		name           string
		permissionSets []string
	}{
		{"empty slice", []string{}},
		{"single permission", []string{"admin"}},
		{"multiple permissions", []string{"admin", "viewer", "editor"}},
		{"special characters", []string{"DBaaS-Prod-ViewOnly-Role", "PingCAP-SecurityAdmin-Role"}},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// Serialize
			jsonData, err := json.Marshal(tc.permissionSets)
			if err != nil {
				t.Fatalf("failed to marshal permission sets: %v", err)
			}

			// Deserialize
			var result []string
			err = json.Unmarshal(jsonData, &result)
			if err != nil {
				t.Fatalf("failed to unmarshal permission sets: %v", err)
			}

			// Compare
			if len(result) != len(tc.permissionSets) {
				t.Errorf("length mismatch: got %d, want %d", len(result), len(tc.permissionSets))
			}
			for i, v := range tc.permissionSets {
				if i < len(result) && result[i] != v {
					t.Errorf("value mismatch at index %d: got %s, want %s", i, result[i], v)
				}
			}
		})
	}
}

// TestDatabaseConfigDSN tests that the DSN is correctly built from config.
func TestDatabaseConfigDSN(t *testing.T) {
	cfg := &config.DatabaseConfig{
		Host:         "localhost",
		Port:         3306,
		User:         "testuser",
		Password:     "testpass",
		Database:     "testdb",
		MaxOpenConns: 10,
		MaxIdleConns: 5,
		MaxLifetime:  time.Hour,
	}

	// Verify the config values are set correctly
	if cfg.Host != "localhost" {
		t.Errorf("expected Host to be localhost, got %s", cfg.Host)
	}
	if cfg.Port != 3306 {
		t.Errorf("expected Port to be 3306, got %d", cfg.Port)
	}
	if cfg.User != "testuser" {
		t.Errorf("expected User to be testuser, got %s", cfg.User)
	}
	if cfg.Database != "testdb" {
		t.Errorf("expected Database to be testdb, got %s", cfg.Database)
	}
	if cfg.MaxOpenConns != 10 {
		t.Errorf("expected MaxOpenConns to be 10, got %d", cfg.MaxOpenConns)
	}
	if cfg.MaxIdleConns != 5 {
		t.Errorf("expected MaxIdleConns to be 5, got %d", cfg.MaxIdleConns)
	}
	if cfg.MaxLifetime != time.Hour {
		t.Errorf("expected MaxLifetime to be 1h, got %v", cfg.MaxLifetime)
	}
}

// TestMySQLStoreImplementsStoreInterface verifies that MySQLStore implements the Store interface.
func TestMySQLStoreImplementsStoreInterface(t *testing.T) {
	// This is a compile-time check, but we include it as a test for documentation
	var _ Store = (*MySQLStore)(nil)
}

// TestContextCancellation tests that operations respect context cancellation.
func TestContextCancellation(t *testing.T) {
	// Create a cancelled context
	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	// Test that the context is actually cancelled
	if ctx.Err() == nil {
		t.Error("expected context to be cancelled")
	}
	if !errors.Is(ctx.Err(), context.Canceled) {
		t.Errorf("expected context.Canceled error, got %v", ctx.Err())
	}
}

// TestMySQLStore_ConnectionPoolConfig tests that connection pool settings are applied correctly.
func TestMySQLStore_ConnectionPoolConfig(t *testing.T) {
	// Test various connection pool configurations
	testCases := []struct {
		name         string
		maxOpenConns int
		maxIdleConns int
		maxLifetime  time.Duration
	}{
		{"default config", 10, 5, time.Hour},
		{"high concurrency", 50, 25, 30 * time.Minute},
		{"low concurrency", 5, 2, 2 * time.Hour},
		{"minimal", 1, 1, time.Minute},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			cfg := &config.DatabaseConfig{
				Host:         "localhost",
				Port:         3306,
				User:         "test",
				Password:     "test",
				Database:     "test",
				MaxOpenConns: tc.maxOpenConns,
				MaxIdleConns: tc.maxIdleConns,
				MaxLifetime:  tc.maxLifetime,
			}

			// Verify config values
			if cfg.MaxOpenConns != tc.maxOpenConns {
				t.Errorf("MaxOpenConns = %d, want %d", cfg.MaxOpenConns, tc.maxOpenConns)
			}
			if cfg.MaxIdleConns != tc.maxIdleConns {
				t.Errorf("MaxIdleConns = %d, want %d", cfg.MaxIdleConns, tc.maxIdleConns)
			}
			if cfg.MaxLifetime != tc.maxLifetime {
				t.Errorf("MaxLifetime = %v, want %v", cfg.MaxLifetime, tc.maxLifetime)
			}
		})
	}
}

// TestMySQLStore_QueryBuilding tests that query building with filters works correctly.
func TestMySQLStore_QueryBuilding(t *testing.T) {
	// Test filter combinations
	testCases := []struct {
		name           string
		filter         *InstanceFilter
		expectedFields int // Number of filter fields that should be applied
	}{
		{"nil filter", nil, 0},
		{"empty filter", &InstanceFilter{}, 0},
		{"status only", &InstanceFilter{Status: StatusApproved}, 1},
		{"cloud type only", &InstanceFilter{CloudType: CloudTypeAWS}, 1},
		{"email only", &InstanceFilter{Email: "test@example.com"}, 1},
		{"account ID only", &InstanceFilter{AccountID: "123456"}, 1},
		{"multiple filters", &InstanceFilter{
			Status:    StatusApproved,
			CloudType: CloudTypeAWS,
			Email:     "test@example.com",
		}, 3},
		{"with pagination", &InstanceFilter{
			Status: StatusApproved,
			Limit:  10,
			Offset: 20,
		}, 1}, // Only status is a filter field, limit/offset are pagination
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// Count non-empty filter fields
			count := 0
			if tc.filter != nil {
				if tc.filter.Status != "" {
					count++
				}
				if tc.filter.CloudType != "" {
					count++
				}
				if tc.filter.Email != "" {
					count++
				}
				if tc.filter.AccountID != "" {
					count++
				}
				if !tc.filter.StartTime.IsZero() {
					count++
				}
				if !tc.filter.EndTime.IsZero() {
					count++
				}
			}

			if count != tc.expectedFields {
				t.Errorf("filter field count = %d, want %d", count, tc.expectedFields)
			}
		})
	}
}

// TestMySQLStore_TransactionBehavior documents expected transaction behavior.
func TestMySQLStore_TransactionBehavior(t *testing.T) {
	// Document expected transaction behavior:
	// 1. WithTransaction should begin a transaction
	// 2. If the function succeeds, commit the transaction
	// 3. If the function fails, rollback the transaction
	// 4. If a panic occurs, rollback and re-panic

	// This is a documentation test - actual transaction testing requires a database
	t.Log("Transaction behavior documented:")
	t.Log("- Begin transaction before executing function")
	t.Log("- Commit on success")
	t.Log("- Rollback on error")
	t.Log("- Rollback and re-panic on panic")
}

// TestMySQLStore_ErrorWrapping tests that errors are properly wrapped.
func TestMySQLStore_ErrorWrapping(t *testing.T) {
	store := &MySQLStore{}

	// Test that validation errors are returned directly (not wrapped)
	err := store.SaveInstance(context.Background(), nil)
	if err == nil {
		t.Fatal("expected error")
	}
	if err.Error() != "instance is required" {
		t.Errorf("unexpected error: %v", err)
	}

	// Test empty ID errors
	_, err = store.GetInstance(context.Background(), "")
	if err == nil {
		t.Fatal("expected error")
	}
	if err.Error() != "instance ID is required" {
		t.Errorf("unexpected error: %v", err)
	}
}

// BenchmarkPermissionSetsJSONSerialization benchmarks JSON serialization of permission sets.
func BenchmarkPermissionSetsJSONSerialization(b *testing.B) {
	permissionSets := []string{
		"DBaaS-Prod-ViewOnly-Role",
		"DBaaS-Prod-OnCall-Role",
		"DBaaS-Prod-Billing-Role",
		"DBaaS-Prod-Admin-Role",
		"PingCAP-SecurityAdmin-Role",
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		data, _ := json.Marshal(permissionSets)
		var result []string
		json.Unmarshal(data, &result)
	}
}

// BenchmarkFilterIsEmpty benchmarks the IsEmpty method on InstanceFilter.
func BenchmarkFilterIsEmpty(b *testing.B) {
	filter := &InstanceFilter{
		Status:    StatusApproved,
		CloudType: CloudTypeAWS,
		Email:     "test@example.com",
		Limit:     10,
		Offset:    20,
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = filter.IsEmpty()
	}
}
