package test

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/google/uuid"
	"github.com/kms/api-key-service/internal/domain"
	"github.com/kms/api-key-service/internal/repository"
)

// MockDatabaseProvider implements DatabaseProvider for testing
type MockDatabaseProvider struct {
	mu sync.RWMutex
}

func NewMockDatabaseProvider() repository.DatabaseProvider {
	return &MockDatabaseProvider{}
}

func (m *MockDatabaseProvider) GetDB() interface{} {
	return m
}

func (m *MockDatabaseProvider) Ping(ctx context.Context) error {
	return nil
}

func (m *MockDatabaseProvider) Close() error {
	return nil
}

func (m *MockDatabaseProvider) BeginTx(ctx context.Context) (repository.TransactionProvider, error) {
	return &MockTransactionProvider{}, nil
}

func (m *MockDatabaseProvider) Migrate(ctx context.Context, migrationPath string) error {
	return nil
}

// MockTransactionProvider implements TransactionProvider for testing
type MockTransactionProvider struct{}

func (m *MockTransactionProvider) Commit() error {
	return nil
}

func (m *MockTransactionProvider) Rollback() error {
	return nil
}

func (m *MockTransactionProvider) GetTx() interface{} {
	return m
}

// MockApplicationRepository implements ApplicationRepository for testing
type MockApplicationRepository struct {
	mu           sync.RWMutex
	applications map[string]*domain.Application
}

func NewMockApplicationRepository() repository.ApplicationRepository {
	return &MockApplicationRepository{
		applications: make(map[string]*domain.Application),
	}
}

func (m *MockApplicationRepository) Create(ctx context.Context, app *domain.Application) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	if _, exists := m.applications[app.AppID]; exists {
		return fmt.Errorf("application with ID '%s' already exists", app.AppID)
	}

	now := time.Now()
	app.CreatedAt = now
	app.UpdatedAt = now

	// Make a copy to avoid reference issues
	appCopy := *app
	m.applications[app.AppID] = &appCopy

	return nil
}

func (m *MockApplicationRepository) GetByID(ctx context.Context, appID string) (*domain.Application, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	app, exists := m.applications[appID]
	if !exists {
		return nil, fmt.Errorf("application with ID '%s' not found", appID)
	}

	// Return a copy to avoid reference issues
	appCopy := *app
	return &appCopy, nil
}

func (m *MockApplicationRepository) List(ctx context.Context, limit, offset int) ([]*domain.Application, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	var apps []*domain.Application
	i := 0
	for _, app := range m.applications {
		if i < offset {
			i++
			continue
		}
		if len(apps) >= limit {
			break
		}
		// Return a copy to avoid reference issues
		appCopy := *app
		apps = append(apps, &appCopy)
		i++
	}

	return apps, nil
}

func (m *MockApplicationRepository) Update(ctx context.Context, appID string, updates *domain.UpdateApplicationRequest) (*domain.Application, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	app, exists := m.applications[appID]
	if !exists {
		return nil, fmt.Errorf("application with ID '%s' not found", appID)
	}

	// Apply updates
	if updates.AppLink != nil {
		app.AppLink = *updates.AppLink
	}
	if updates.Type != nil {
		app.Type = *updates.Type
	}
	if updates.CallbackURL != nil {
		app.CallbackURL = *updates.CallbackURL
	}
	if updates.HMACKey != nil {
		app.HMACKey = *updates.HMACKey
	}
	if updates.TokenRenewalDuration != nil {
		app.TokenRenewalDuration = *updates.TokenRenewalDuration
	}
	if updates.MaxTokenDuration != nil {
		app.MaxTokenDuration = *updates.MaxTokenDuration
	}
	if updates.Owner != nil {
		app.Owner = *updates.Owner
	}

	app.UpdatedAt = time.Now()

	// Return a copy
	appCopy := *app
	return &appCopy, nil
}

func (m *MockApplicationRepository) Delete(ctx context.Context, appID string) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	if _, exists := m.applications[appID]; !exists {
		return fmt.Errorf("application with ID '%s' not found", appID)
	}

	delete(m.applications, appID)
	return nil
}

func (m *MockApplicationRepository) Exists(ctx context.Context, appID string) (bool, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	_, exists := m.applications[appID]
	return exists, nil
}

// MockStaticTokenRepository implements StaticTokenRepository for testing
type MockStaticTokenRepository struct {
	mu     sync.RWMutex
	tokens map[uuid.UUID]*domain.StaticToken
}

func NewMockStaticTokenRepository() repository.StaticTokenRepository {
	return &MockStaticTokenRepository{
		tokens: make(map[uuid.UUID]*domain.StaticToken),
	}
}

func (m *MockStaticTokenRepository) Create(ctx context.Context, token *domain.StaticToken) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	if token.ID == uuid.Nil {
		token.ID = uuid.New()
	}

	now := time.Now()
	token.CreatedAt = now
	token.UpdatedAt = now

	// Make a copy
	tokenCopy := *token
	m.tokens[token.ID] = &tokenCopy

	return nil
}

func (m *MockStaticTokenRepository) GetByID(ctx context.Context, tokenID uuid.UUID) (*domain.StaticToken, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	token, exists := m.tokens[tokenID]
	if !exists {
		return nil, fmt.Errorf("token with ID '%s' not found", tokenID)
	}

	tokenCopy := *token
	return &tokenCopy, nil
}

func (m *MockStaticTokenRepository) GetByKeyHash(ctx context.Context, keyHash string) (*domain.StaticToken, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	for _, token := range m.tokens {
		if token.KeyHash == keyHash {
			tokenCopy := *token
			return &tokenCopy, nil
		}
	}

	return nil, fmt.Errorf("token with key hash not found")
}

func (m *MockStaticTokenRepository) GetByAppID(ctx context.Context, appID string) ([]*domain.StaticToken, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	var tokens []*domain.StaticToken
	for _, token := range m.tokens {
		if token.AppID == appID {
			tokenCopy := *token
			tokens = append(tokens, &tokenCopy)
		}
	}

	return tokens, nil
}

func (m *MockStaticTokenRepository) List(ctx context.Context, limit, offset int) ([]*domain.StaticToken, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	var tokens []*domain.StaticToken
	i := 0
	for _, token := range m.tokens {
		if i < offset {
			i++
			continue
		}
		if len(tokens) >= limit {
			break
		}
		tokenCopy := *token
		tokens = append(tokens, &tokenCopy)
		i++
	}

	return tokens, nil
}

func (m *MockStaticTokenRepository) Delete(ctx context.Context, tokenID uuid.UUID) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	if _, exists := m.tokens[tokenID]; !exists {
		return fmt.Errorf("token with ID '%s' not found", tokenID)
	}

	delete(m.tokens, tokenID)
	return nil
}

func (m *MockStaticTokenRepository) Exists(ctx context.Context, tokenID uuid.UUID) (bool, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	_, exists := m.tokens[tokenID]
	return exists, nil
}

// MockPermissionRepository implements PermissionRepository for testing
type MockPermissionRepository struct {
	mu          sync.RWMutex
	permissions map[uuid.UUID]*domain.AvailablePermission
	scopeIndex  map[string]uuid.UUID
}

func NewMockPermissionRepository() repository.PermissionRepository {
	repo := &MockPermissionRepository{
		permissions: make(map[uuid.UUID]*domain.AvailablePermission),
		scopeIndex:  make(map[string]uuid.UUID),
	}

	// Add some default permissions for testing
	ctx := context.Background()
	defaultPerms := []*domain.AvailablePermission{
		{
			ID:          uuid.New(),
			Scope:       "repo.read",
			Name:        "Repository Read",
			Description: "Read repository data",
			Category:    "repository",
			IsSystem:    false,
			CreatedAt:   time.Now(),
			CreatedBy:   "system",
			UpdatedAt:   time.Now(),
			UpdatedBy:   "system",
		},
		{
			ID:          uuid.New(),
			Scope:       "repo.write",
			Name:        "Repository Write",
			Description: "Write to repositories",
			Category:    "repository",
			IsSystem:    false,
			CreatedAt:   time.Now(),
			CreatedBy:   "system",
			UpdatedAt:   time.Now(),
			UpdatedBy:   "system",
		},
	}

	for _, perm := range defaultPerms {
		repo.CreateAvailablePermission(ctx, perm)
	}

	return repo
}

func (m *MockPermissionRepository) CreateAvailablePermission(ctx context.Context, permission *domain.AvailablePermission) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	if permission.ID == uuid.Nil {
		permission.ID = uuid.New()
	}

	if _, exists := m.scopeIndex[permission.Scope]; exists {
		return fmt.Errorf("permission with scope '%s' already exists", permission.Scope)
	}

	now := time.Now()
	permission.CreatedAt = now
	permission.UpdatedAt = now

	permCopy := *permission
	m.permissions[permission.ID] = &permCopy
	m.scopeIndex[permission.Scope] = permission.ID

	return nil
}

func (m *MockPermissionRepository) GetAvailablePermission(ctx context.Context, permissionID uuid.UUID) (*domain.AvailablePermission, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	perm, exists := m.permissions[permissionID]
	if !exists {
		return nil, fmt.Errorf("permission with ID '%s' not found", permissionID)
	}

	permCopy := *perm
	return &permCopy, nil
}

func (m *MockPermissionRepository) GetAvailablePermissionByScope(ctx context.Context, scope string) (*domain.AvailablePermission, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	permID, exists := m.scopeIndex[scope]
	if !exists {
		return nil, fmt.Errorf("permission with scope '%s' not found", scope)
	}

	perm := m.permissions[permID]
	permCopy := *perm
	return &permCopy, nil
}

func (m *MockPermissionRepository) ListAvailablePermissions(ctx context.Context, category string, includeSystem bool, limit, offset int) ([]*domain.AvailablePermission, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	var perms []*domain.AvailablePermission
	i := 0
	for _, perm := range m.permissions {
		if category != "" && perm.Category != category {
			continue
		}
		if !includeSystem && perm.IsSystem {
			continue
		}
		if i < offset {
			i++
			continue
		}
		if len(perms) >= limit {
			break
		}
		permCopy := *perm
		perms = append(perms, &permCopy)
		i++
	}

	return perms, nil
}

func (m *MockPermissionRepository) UpdateAvailablePermission(ctx context.Context, permissionID uuid.UUID, permission *domain.AvailablePermission) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	if _, exists := m.permissions[permissionID]; !exists {
		return fmt.Errorf("permission with ID '%s' not found", permissionID)
	}

	permission.ID = permissionID
	permission.UpdatedAt = time.Now()

	permCopy := *permission
	m.permissions[permissionID] = &permCopy

	return nil
}

func (m *MockPermissionRepository) DeleteAvailablePermission(ctx context.Context, permissionID uuid.UUID) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	perm, exists := m.permissions[permissionID]
	if !exists {
		return fmt.Errorf("permission with ID '%s' not found", permissionID)
	}

	delete(m.permissions, permissionID)
	delete(m.scopeIndex, perm.Scope)

	return nil
}

func (m *MockPermissionRepository) ValidatePermissionScopes(ctx context.Context, scopes []string) ([]string, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	var valid []string
	for _, scope := range scopes {
		if _, exists := m.scopeIndex[scope]; exists {
			valid = append(valid, scope)
		}
	}

	return valid, nil
}

func (m *MockPermissionRepository) GetPermissionHierarchy(ctx context.Context, scopes []string) ([]*domain.AvailablePermission, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	var perms []*domain.AvailablePermission
	for _, scope := range scopes {
		if permID, exists := m.scopeIndex[scope]; exists {
			perm := m.permissions[permID]
			permCopy := *perm
			perms = append(perms, &permCopy)
		}
	}

	return perms, nil
}

// MockGrantedPermissionRepository implements GrantedPermissionRepository for testing
type MockGrantedPermissionRepository struct {
	mu     sync.RWMutex
	grants map[uuid.UUID]*domain.GrantedPermission
}

func NewMockGrantedPermissionRepository() repository.GrantedPermissionRepository {
	return &MockGrantedPermissionRepository{
		grants: make(map[uuid.UUID]*domain.GrantedPermission),
	}
}

func (m *MockGrantedPermissionRepository) GrantPermissions(ctx context.Context, grants []*domain.GrantedPermission) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	for _, grant := range grants {
		if grant.ID == uuid.Nil {
			grant.ID = uuid.New()
		}
		grant.CreatedAt = time.Now()

		grantCopy := *grant
		m.grants[grant.ID] = &grantCopy
	}

	return nil
}

func (m *MockGrantedPermissionRepository) GetGrantedPermissions(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID) ([]*domain.GrantedPermission, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	var grants []*domain.GrantedPermission
	for _, grant := range m.grants {
		if grant.TokenType == tokenType && grant.TokenID == tokenID && !grant.Revoked {
			grantCopy := *grant
			grants = append(grants, &grantCopy)
		}
	}

	return grants, nil
}

func (m *MockGrantedPermissionRepository) GetGrantedPermissionScopes(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID) ([]string, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	var scopes []string
	for _, grant := range m.grants {
		if grant.TokenType == tokenType && grant.TokenID == tokenID && !grant.Revoked {
			scopes = append(scopes, grant.Scope)
		}
	}

	return scopes, nil
}

func (m *MockGrantedPermissionRepository) RevokePermission(ctx context.Context, grantID uuid.UUID, revokedBy string) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	grant, exists := m.grants[grantID]
	if !exists {
		return fmt.Errorf("granted permission with ID '%s' not found", grantID)
	}

	grant.Revoked = true
	return nil
}

func (m *MockGrantedPermissionRepository) RevokeAllPermissions(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID, revokedBy string) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	for _, grant := range m.grants {
		if grant.TokenType == tokenType && grant.TokenID == tokenID {
			grant.Revoked = true
		}
	}

	return nil
}

func (m *MockGrantedPermissionRepository) HasPermission(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID, scope string) (bool, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	for _, grant := range m.grants {
		if grant.TokenType == tokenType && grant.TokenID == tokenID && grant.Scope == scope && !grant.Revoked {
			return true, nil
		}
	}

	return false, nil
}

func (m *MockGrantedPermissionRepository) HasAnyPermission(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID, scopes []string) (map[string]bool, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	result := make(map[string]bool)
	for _, scope := range scopes {
		result[scope] = false
		for _, grant := range m.grants {
			if grant.TokenType == tokenType && grant.TokenID == tokenID && grant.Scope == scope && !grant.Revoked {
				result[scope] = true
				break
			}
		}
	}

	return result, nil
}
