package secrets

import (
	"context"
	"testing"
	"time"

	"github.com/alecthomas/assert/v2"
	"github.com/bluesky-social/indigo/atproto/syntax"
)

func createInMemoryDB(t *testing.T) *SqliteManager {
	t.Helper()
	manager, err := NewSQLiteManager(":memory:")
	if err != nil {
		t.Fatalf("Failed to create in-memory manager: %v", err)
	}
	return manager
}

func createTestSecret(repo, key, value, createdBy string) UnlockedSecret {
	return UnlockedSecret{
		Key:       key,
		Value:     value,
		Repo:      RepoIdentifier(repo),
		CreatedAt: time.Now(),
		CreatedBy: syntax.DID(createdBy),
	}
}

// ensure that interface is satisfied
func TestManagerInterface(t *testing.T) {
	var _ Manager = (*SqliteManager)(nil)
}

func TestNewSQLiteManager(t *testing.T) {
	tests := []struct {
		name        string
		dbPath      string
		opts        []SqliteManagerOpt
		expectError bool
		expectTable string
	}{
		{
			name:        "default table name",
			dbPath:      ":memory:",
			opts:        nil,
			expectError: false,
			expectTable: "secrets",
		},
		{
			name:        "custom table name",
			dbPath:      ":memory:",
			opts:        []SqliteManagerOpt{WithTableName("custom_secrets")},
			expectError: false,
			expectTable: "custom_secrets",
		},
		{
			name:        "invalid database path",
			dbPath:      "/invalid/path/to/database.db",
			opts:        nil,
			expectError: true,
			expectTable: "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			manager, err := NewSQLiteManager(tt.dbPath, tt.opts...)
			if tt.expectError {
				if err == nil {
					t.Error("Expected error but got none")
				}
				return
			}

			if err != nil {
				t.Fatalf("Unexpected error: %v", err)
			}
			defer manager.db.Close()

			if manager.tableName != tt.expectTable {
				t.Errorf("Expected table name %s, got %s", tt.expectTable, manager.tableName)
			}
		})
	}
}

func TestSqliteManager_AddSecret(t *testing.T) {
	tests := []struct {
		name        string
		secrets     []UnlockedSecret
		expectError []error
	}{
		{
			name: "add single secret",
			secrets: []UnlockedSecret{
				createTestSecret("did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"),
			},
			expectError: []error{nil},
		},
		{
			name: "add multiple unique secrets",
			secrets: []UnlockedSecret{
				createTestSecret("did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"),
				createTestSecret("did:plc:foo/repo", "db_password", "password_456", "did:plc:example123"),
				createTestSecret("other.com/repo", "api_key", "other_secret", "did:plc:other"),
			},
			expectError: []error{nil, nil, nil},
		},
		{
			name: "add duplicate secret",
			secrets: []UnlockedSecret{
				createTestSecret("did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"),
				createTestSecret("did:plc:foo/repo", "api_key", "different_value", "did:plc:example123"),
			},
			expectError: []error{nil, ErrKeyAlreadyPresent},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			manager := createInMemoryDB(t)
			defer manager.db.Close()

			for i, secret := range tt.secrets {
				err := manager.AddSecret(context.Background(), secret)
				if err != tt.expectError[i] {
					t.Errorf("Secret %d: expected error %v, got %v", i, tt.expectError[i], err)
				}
			}
		})
	}
}

func TestSqliteManager_RemoveSecret(t *testing.T) {
	tests := []struct {
		name         string
		setupSecrets []UnlockedSecret
		removeSecret Secret[any]
		expectError  error
	}{
		{
			name: "remove existing secret",
			setupSecrets: []UnlockedSecret{
				createTestSecret("did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"),
			},
			removeSecret: Secret[any]{
				Key:  "api_key",
				Repo: RepoIdentifier("did:plc:foo/repo"),
			},
			expectError: nil,
		},
		{
			name: "remove non-existent secret",
			setupSecrets: []UnlockedSecret{
				createTestSecret("did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"),
			},
			removeSecret: Secret[any]{
				Key:  "non_existent_key",
				Repo: RepoIdentifier("did:plc:foo/repo"),
			},
			expectError: ErrKeyNotFound,
		},
		{
			name:         "remove from empty database",
			setupSecrets: []UnlockedSecret{},
			removeSecret: Secret[any]{
				Key:  "any_key",
				Repo: RepoIdentifier("did:plc:foo/repo"),
			},
			expectError: ErrKeyNotFound,
		},
		{
			name: "remove secret from wrong repo",
			setupSecrets: []UnlockedSecret{
				createTestSecret("did:plc:foo/repo", "api_key", "secret_value_123", "did:plc:example123"),
			},
			removeSecret: Secret[any]{
				Key:  "api_key",
				Repo: RepoIdentifier("other.com/repo"),
			},
			expectError: ErrKeyNotFound,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			manager := createInMemoryDB(t)
			defer manager.db.Close()

			// Setup secrets
			for _, secret := range tt.setupSecrets {
				if err := manager.AddSecret(context.Background(), secret); err != nil {
					t.Fatalf("Failed to setup secret: %v", err)
				}
			}

			// Test removal
			err := manager.RemoveSecret(context.Background(), tt.removeSecret)
			if err != tt.expectError {
				t.Errorf("Expected error %v, got %v", tt.expectError, err)
			}
		})
	}
}

func TestSqliteManager_GetSecretsLocked(t *testing.T) {
	tests := []struct {
		name          string
		setupSecrets  []UnlockedSecret
		queryRepo     RepoIdentifier
		expectedCount int
		expectedKeys  []string
		expectError   bool
	}{
		{
			name: "get secrets for repo with multiple secrets",
			setupSecrets: []UnlockedSecret{
				createTestSecret("did:plc:foo/repo", "key1", "value1", "did:plc:user1"),
				createTestSecret("did:plc:foo/repo", "key2", "value2", "did:plc:user2"),
				createTestSecret("other.com/repo", "key3", "value3", "did:plc:user3"),
			},
			queryRepo:     RepoIdentifier("did:plc:foo/repo"),
			expectedCount: 2,
			expectedKeys:  []string{"key1", "key2"},
			expectError:   false,
		},
		{
			name: "get secrets for repo with single secret",
			setupSecrets: []UnlockedSecret{
				createTestSecret("did:plc:foo/repo", "single_key", "single_value", "did:plc:user1"),
				createTestSecret("other.com/repo", "other_key", "other_value", "did:plc:user2"),
			},
			queryRepo:     RepoIdentifier("did:plc:foo/repo"),
			expectedCount: 1,
			expectedKeys:  []string{"single_key"},
			expectError:   false,
		},
		{
			name: "get secrets for non-existent repo",
			setupSecrets: []UnlockedSecret{
				createTestSecret("did:plc:foo/repo", "key1", "value1", "did:plc:user1"),
			},
			queryRepo:     RepoIdentifier("nonexistent.com/repo"),
			expectedCount: 0,
			expectedKeys:  []string{},
			expectError:   false,
		},
		{
			name:          "get secrets from empty database",
			setupSecrets:  []UnlockedSecret{},
			queryRepo:     RepoIdentifier("did:plc:foo/repo"),
			expectedCount: 0,
			expectedKeys:  []string{},
			expectError:   false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			manager := createInMemoryDB(t)
			defer manager.db.Close()

			// Setup secrets
			for _, secret := range tt.setupSecrets {
				if err := manager.AddSecret(context.Background(), secret); err != nil {
					t.Fatalf("Failed to setup secret: %v", err)
				}
			}

			// Test getting locked secrets
			lockedSecrets, err := manager.GetSecretsLocked(context.Background(), tt.queryRepo)
			if tt.expectError && err == nil {
				t.Error("Expected error but got none")
				return
			}
			if !tt.expectError && err != nil {
				t.Fatalf("Unexpected error: %v", err)
			}

			if len(lockedSecrets) != tt.expectedCount {
				t.Errorf("Expected %d secrets, got %d", tt.expectedCount, len(lockedSecrets))
			}

			// Verify keys and that values are not present (locked)
			foundKeys := make(map[string]bool)
			for _, ls := range lockedSecrets {
				foundKeys[ls.Key] = true
				if ls.Repo != tt.queryRepo {
					t.Errorf("Expected repo %s, got %s", tt.queryRepo, ls.Repo)
				}
				if ls.CreatedBy == "" {
					t.Error("Expected CreatedBy to be present")
				}
				if ls.CreatedAt.IsZero() {
					t.Error("Expected CreatedAt to be set")
				}
			}

			for _, expectedKey := range tt.expectedKeys {
				if !foundKeys[expectedKey] {
					t.Errorf("Expected key %s not found", expectedKey)
				}
			}
		})
	}
}

func TestSqliteManager_GetSecretsUnlocked(t *testing.T) {
	tests := []struct {
		name            string
		setupSecrets    []UnlockedSecret
		queryRepo       RepoIdentifier
		expectedCount   int
		expectedSecrets map[string]string // key -> value
		expectError     bool
	}{
		{
			name: "get unlocked secrets for repo with multiple secrets",
			setupSecrets: []UnlockedSecret{
				createTestSecret("did:plc:foo/repo", "key1", "value1", "did:plc:user1"),
				createTestSecret("did:plc:foo/repo", "key2", "value2", "did:plc:user2"),
				createTestSecret("other.com/repo", "key3", "value3", "did:plc:user3"),
			},
			queryRepo:     RepoIdentifier("did:plc:foo/repo"),
			expectedCount: 2,
			expectedSecrets: map[string]string{
				"key1": "value1",
				"key2": "value2",
			},
			expectError: false,
		},
		{
			name: "get unlocked secrets for repo with single secret",
			setupSecrets: []UnlockedSecret{
				createTestSecret("did:plc:foo/repo", "single_key", "single_value", "did:plc:user1"),
				createTestSecret("other.com/repo", "other_key", "other_value", "did:plc:user2"),
			},
			queryRepo:     RepoIdentifier("did:plc:foo/repo"),
			expectedCount: 1,
			expectedSecrets: map[string]string{
				"single_key": "single_value",
			},
			expectError: false,
		},
		{
			name: "get unlocked secrets for non-existent repo",
			setupSecrets: []UnlockedSecret{
				createTestSecret("did:plc:foo/repo", "key1", "value1", "did:plc:user1"),
			},
			queryRepo:       RepoIdentifier("nonexistent.com/repo"),
			expectedCount:   0,
			expectedSecrets: map[string]string{},
			expectError:     false,
		},
		{
			name:            "get unlocked secrets from empty database",
			setupSecrets:    []UnlockedSecret{},
			queryRepo:       RepoIdentifier("did:plc:foo/repo"),
			expectedCount:   0,
			expectedSecrets: map[string]string{},
			expectError:     false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			manager := createInMemoryDB(t)
			defer manager.db.Close()

			// Setup secrets
			for _, secret := range tt.setupSecrets {
				if err := manager.AddSecret(context.Background(), secret); err != nil {
					t.Fatalf("Failed to setup secret: %v", err)
				}
			}

			// Test getting unlocked secrets
			unlockedSecrets, err := manager.GetSecretsUnlocked(context.Background(), tt.queryRepo)
			if tt.expectError && err == nil {
				t.Error("Expected error but got none")
				return
			}
			if !tt.expectError && err != nil {
				t.Fatalf("Unexpected error: %v", err)
			}

			if len(unlockedSecrets) != tt.expectedCount {
				t.Errorf("Expected %d secrets, got %d", tt.expectedCount, len(unlockedSecrets))
			}

			// Verify keys, values, and metadata
			for _, us := range unlockedSecrets {
				expectedValue, exists := tt.expectedSecrets[us.Key]
				if !exists {
					t.Errorf("Unexpected key: %s", us.Key)
					continue
				}
				if us.Value != expectedValue {
					t.Errorf("Expected value %s for key %s, got %s", expectedValue, us.Key, us.Value)
				}
				if us.Repo != tt.queryRepo {
					t.Errorf("Expected repo %s, got %s", tt.queryRepo, us.Repo)
				}
				if us.CreatedBy == "" {
					t.Error("Expected CreatedBy to be present")
				}
				if us.CreatedAt.IsZero() {
					t.Error("Expected CreatedAt to be set")
				}
			}
		})
	}
}

// Test that demonstrates interface usage with table-driven tests
func TestManagerInterface_Usage(t *testing.T) {
	tests := []struct {
		name        string
		operations  []func(Manager) error
		expectError bool
	}{
		{
			name: "successful workflow",
			operations: []func(Manager) error{
				func(m Manager) error {
					secret := createTestSecret("interface.test/repo", "test_key", "test_value", "did:plc:user")
					return m.AddSecret(context.Background(), secret)
				},
				func(m Manager) error {
					_, err := m.GetSecretsLocked(context.Background(), RepoIdentifier("interface.test/repo"))
					return err
				},
				func(m Manager) error {
					_, err := m.GetSecretsUnlocked(context.Background(), RepoIdentifier("interface.test/repo"))
					return err
				},
				func(m Manager) error {
					secret := Secret[any]{
						Key:  "test_key",
						Repo: RepoIdentifier("interface.test/repo"),
					}
					return m.RemoveSecret(context.Background(), secret)
				},
			},
			expectError: false,
		},
		{
			name: "error on duplicate key",
			operations: []func(Manager) error{
				func(m Manager) error {
					secret := createTestSecret("interface.test/repo", "dup_key", "value1", "did:plc:user")
					return m.AddSecret(context.Background(), secret)
				},
				func(m Manager) error {
					secret := createTestSecret("interface.test/repo", "dup_key", "value2", "did:plc:user")
					return m.AddSecret(context.Background(), secret) // Should return ErrKeyAlreadyPresent
				},
			},
			expectError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var manager Manager = createInMemoryDB(t)
			defer func() {
				if sqliteManager, ok := manager.(*SqliteManager); ok {
					sqliteManager.db.Close()
				}
			}()

			var finalErr error
			for i, operation := range tt.operations {
				if err := operation(manager); err != nil {
					finalErr = err
					t.Logf("Operation %d returned error: %v", i, err)
				}
			}

			if tt.expectError && finalErr == nil {
				t.Error("Expected error but got none")
			}
			if !tt.expectError && finalErr != nil {
				t.Errorf("Unexpected error: %v", finalErr)
			}
		})
	}
}

// Integration test with table-driven scenarios
func TestSqliteManager_Integration(t *testing.T) {
	tests := []struct {
		name     string
		scenario func(*testing.T, *SqliteManager)
	}{
		{
			name: "multi-repo secret management",
			scenario: func(t *testing.T, manager *SqliteManager) {
				repo1 := RepoIdentifier("example1.com/repo")
				repo2 := RepoIdentifier("example2.com/repo")

				secrets := []UnlockedSecret{
					createTestSecret(string(repo1), "db_password", "super_secret_123", "did:plc:admin"),
					createTestSecret(string(repo1), "api_key", "api_key_456", "did:plc:user1"),
					createTestSecret(string(repo2), "token", "bearer_token_789", "did:plc:user2"),
				}

				// Add all secrets
				for _, secret := range secrets {
					if err := manager.AddSecret(context.Background(), secret); err != nil {
						t.Fatalf("Failed to add secret %s: %v", secret.Key, err)
					}
				}

				// Verify counts
				locked1, _ := manager.GetSecretsLocked(context.Background(), repo1)
				locked2, _ := manager.GetSecretsLocked(context.Background(), repo2)

				if len(locked1) != 2 {
					t.Errorf("Expected 2 secrets for repo1, got %d", len(locked1))
				}
				if len(locked2) != 1 {
					t.Errorf("Expected 1 secret for repo2, got %d", len(locked2))
				}

				// Remove and verify
				secretToRemove := Secret[any]{Key: "db_password", Repo: repo1}
				if err := manager.RemoveSecret(context.Background(), secretToRemove); err != nil {
					t.Fatalf("Failed to remove secret: %v", err)
				}

				locked1After, _ := manager.GetSecretsLocked(context.Background(), repo1)
				if len(locked1After) != 1 {
					t.Errorf("Expected 1 secret for repo1 after removal, got %d", len(locked1After))
				}
				if locked1After[0].Key != "api_key" {
					t.Errorf("Expected remaining secret to be 'api_key', got %s", locked1After[0].Key)
				}
			},
		},
		{
			name: "empty database operations",
			scenario: func(t *testing.T, manager *SqliteManager) {
				repo := RepoIdentifier("empty.test/repo")

				// Operations on empty database should not error
				locked, err := manager.GetSecretsLocked(context.Background(), repo)
				if err != nil {
					t.Errorf("GetSecretsLocked on empty DB failed: %v", err)
				}
				if len(locked) != 0 {
					t.Errorf("Expected 0 secrets, got %d", len(locked))
				}

				unlocked, err := manager.GetSecretsUnlocked(context.Background(), repo)
				if err != nil {
					t.Errorf("GetSecretsUnlocked on empty DB failed: %v", err)
				}
				if len(unlocked) != 0 {
					t.Errorf("Expected 0 secrets, got %d", len(unlocked))
				}

				// Remove from empty should return ErrKeyNotFound
				nonExistent := Secret[any]{Key: "none", Repo: repo}
				err = manager.RemoveSecret(context.Background(), nonExistent)
				if err != ErrKeyNotFound {
					t.Errorf("Expected ErrKeyNotFound, got %v", err)
				}
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			manager := createInMemoryDB(t)
			defer manager.db.Close()
			tt.scenario(t, manager)
		})
	}
}

func TestSqliteManager_StopperInterface(t *testing.T) {
	manager := &SqliteManager{}

	// Verify that SqliteManager does NOT implement the Stopper interface
	_, ok := interface{}(manager).(Stopper)
	assert.False(t, ok, "SqliteManager should NOT implement Stopper interface")
}
