package postgres

import (
	"context"
	"database/sql"
	"fmt"
	"time"

	"github.com/google/uuid"
	"github.com/RyanCopley/skybridge/kms/internal/domain"
	"github.com/RyanCopley/skybridge/kms/internal/repository"
	"github.com/lib/pq"
)

// PermissionRepository implements the PermissionRepository interface for PostgreSQL
type PermissionRepository struct {
	db repository.DatabaseProvider
}

// NewPermissionRepository creates a new PostgreSQL permission repository
func NewPermissionRepository(db repository.DatabaseProvider) repository.PermissionRepository {
	return &PermissionRepository{db: db}
}

// CreateAvailablePermission creates a new available permission
func (r *PermissionRepository) CreateAvailablePermission(ctx context.Context, permission *domain.AvailablePermission) error {
	query := `
		INSERT INTO available_permissions (
			id, scope, name, description, category, parent_scope, 
			is_system, created_by, updated_by, created_at, updated_at
		) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
	`

	db := r.db.GetDB().(*sql.DB)
	now := time.Now()

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

	_, err := db.ExecContext(ctx, query,
		permission.ID,
		permission.Scope,
		permission.Name,
		permission.Description,
		permission.Category,
		permission.ParentScope,
		permission.IsSystem,
		permission.CreatedBy,
		permission.UpdatedBy,
		now,
		now,
	)

	if err != nil {
		return fmt.Errorf("failed to create available permission: %w", err)
	}

	permission.CreatedAt = now
	permission.UpdatedAt = now

	return nil
}

// GetAvailablePermission retrieves an available permission by ID
func (r *PermissionRepository) GetAvailablePermission(ctx context.Context, permissionID uuid.UUID) (*domain.AvailablePermission, error) {
	query := `
		SELECT id, scope, name, description, category, parent_scope, 
		       is_system, created_at, created_by, updated_at, updated_by
		FROM available_permissions 
		WHERE id = $1
	`

	db := r.db.GetDB().(*sql.DB)
	row := db.QueryRowContext(ctx, query, permissionID)

	permission := &domain.AvailablePermission{}
	err := row.Scan(
		&permission.ID,
		&permission.Scope,
		&permission.Name,
		&permission.Description,
		&permission.Category,
		&permission.ParentScope,
		&permission.IsSystem,
		&permission.CreatedAt,
		&permission.CreatedBy,
		&permission.UpdatedAt,
		&permission.UpdatedBy,
	)

	if err != nil {
		if err == sql.ErrNoRows {
			return nil, fmt.Errorf("permission with ID '%s' not found", permissionID)
		}
		return nil, fmt.Errorf("failed to get available permission: %w", err)
	}

	return permission, nil
}

// GetAvailablePermissionByScope retrieves an available permission by scope
func (r *PermissionRepository) GetAvailablePermissionByScope(ctx context.Context, scope string) (*domain.AvailablePermission, error) {
	query := `
		SELECT id, scope, name, description, category, parent_scope, 
		       is_system, created_at, created_by, updated_at, updated_by
		FROM available_permissions 
		WHERE scope = $1
	`

	db := r.db.GetDB().(*sql.DB)
	row := db.QueryRowContext(ctx, query, scope)

	permission := &domain.AvailablePermission{}
	err := row.Scan(
		&permission.ID,
		&permission.Scope,
		&permission.Name,
		&permission.Description,
		&permission.Category,
		&permission.ParentScope,
		&permission.IsSystem,
		&permission.CreatedAt,
		&permission.CreatedBy,
		&permission.UpdatedAt,
		&permission.UpdatedBy,
	)

	if err != nil {
		if err == sql.ErrNoRows {
			return nil, fmt.Errorf("permission with scope '%s' not found", scope)
		}
		return nil, fmt.Errorf("failed to get available permission by scope: %w", err)
	}

	return permission, nil
}

// ListAvailablePermissions retrieves available permissions with pagination and filtering
func (r *PermissionRepository) ListAvailablePermissions(ctx context.Context, category string, includeSystem bool, limit, offset int) ([]*domain.AvailablePermission, error) {
	var args []interface{}
	var whereClauses []string
	argIndex := 1

	// Build WHERE clause based on filters
	if category != "" {
		whereClauses = append(whereClauses, fmt.Sprintf("category = $%d", argIndex))
		args = append(args, category)
		argIndex++
	}

	if !includeSystem {
		whereClauses = append(whereClauses, fmt.Sprintf("is_system = $%d", argIndex))
		args = append(args, false)
		argIndex++
	}

	whereClause := ""
	if len(whereClauses) > 0 {
		whereClause = "WHERE " + fmt.Sprintf("%s", whereClauses[0])
		for i := 1; i < len(whereClauses); i++ {
			whereClause += " AND " + whereClauses[i]
		}
	}

	query := fmt.Sprintf(`
		SELECT id, scope, name, description, category, parent_scope,
		       is_system, created_at, created_by, updated_at, updated_by
		FROM available_permissions 
		%s
		ORDER BY category, scope
		LIMIT $%d OFFSET $%d
	`, whereClause, argIndex, argIndex+1)

	args = append(args, limit, offset)

	db := r.db.GetDB().(*sql.DB)
	rows, err := db.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("failed to list available permissions: %w", err)
	}
	defer rows.Close()

	var permissions []*domain.AvailablePermission
	for rows.Next() {
		permission := &domain.AvailablePermission{}
		err := rows.Scan(
			&permission.ID,
			&permission.Scope,
			&permission.Name,
			&permission.Description,
			&permission.Category,
			&permission.ParentScope,
			&permission.IsSystem,
			&permission.CreatedAt,
			&permission.CreatedBy,
			&permission.UpdatedAt,
			&permission.UpdatedBy,
		)
		if err != nil {
			return nil, fmt.Errorf("failed to scan available permission: %w", err)
		}
		permissions = append(permissions, permission)
	}

	if err = rows.Err(); err != nil {
		return nil, fmt.Errorf("failed to iterate available permissions: %w", err)
	}

	return permissions, nil
}

// UpdateAvailablePermission updates an available permission
func (r *PermissionRepository) UpdateAvailablePermission(ctx context.Context, permissionID uuid.UUID, permission *domain.AvailablePermission) error {
	query := `
		UPDATE available_permissions 
		SET scope = $2, name = $3, description = $4, category = $5, 
		    parent_scope = $6, is_system = $7, updated_by = $8, updated_at = $9
		WHERE id = $1
	`

	db := r.db.GetDB().(*sql.DB)
	now := time.Now()

	result, err := db.ExecContext(ctx, query,
		permissionID,
		permission.Scope,
		permission.Name,
		permission.Description,
		permission.Category,
		permission.ParentScope,
		permission.IsSystem,
		permission.UpdatedBy,
		now,
	)

	if err != nil {
		return fmt.Errorf("failed to update available permission: %w", err)
	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("failed to get rows affected: %w", err)
	}

	if rowsAffected == 0 {
		return fmt.Errorf("permission with ID %s not found", permissionID)
	}

	permission.UpdatedAt = now
	return nil
}

// DeleteAvailablePermission deletes an available permission
func (r *PermissionRepository) DeleteAvailablePermission(ctx context.Context, permissionID uuid.UUID) error {
	// First check if the permission has any child permissions
	checkChildrenQuery := `
		SELECT COUNT(*) FROM available_permissions 
		WHERE parent_scope = (SELECT scope FROM available_permissions WHERE id = $1)
	`

	db := r.db.GetDB().(*sql.DB)
	var childCount int
	err := db.QueryRowContext(ctx, checkChildrenQuery, permissionID).Scan(&childCount)
	if err != nil {
		return fmt.Errorf("failed to check for child permissions: %w", err)
	}

	if childCount > 0 {
		return fmt.Errorf("cannot delete permission: it has %d child permissions", childCount)
	}

	// Check if the permission is granted to any tokens
	checkGrantsQuery := `
		SELECT COUNT(*) FROM granted_permissions 
		WHERE permission_id = $1 AND revoked = false
	`

	var grantCount int
	err = db.QueryRowContext(ctx, checkGrantsQuery, permissionID).Scan(&grantCount)
	if err != nil {
		return fmt.Errorf("failed to check for active grants: %w", err)
	}

	if grantCount > 0 {
		return fmt.Errorf("cannot delete permission: it is currently granted to %d tokens", grantCount)
	}

	// Delete the permission
	deleteQuery := `DELETE FROM available_permissions WHERE id = $1`
	result, err := db.ExecContext(ctx, deleteQuery, permissionID)
	if err != nil {
		return fmt.Errorf("failed to delete available permission: %w", err)
	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("failed to get rows affected: %w", err)
	}

	if rowsAffected == 0 {
		return fmt.Errorf("permission with ID %s not found", permissionID)
	}

	return nil
}

// ValidatePermissionScopes checks if all given scopes exist and are valid
func (r *PermissionRepository) ValidatePermissionScopes(ctx context.Context, scopes []string) ([]string, error) {
	if len(scopes) == 0 {
		return []string{}, nil
	}

	query := `
		SELECT scope 
		FROM available_permissions 
		WHERE scope = ANY($1)
	`

	db := r.db.GetDB().(*sql.DB)
	rows, err := db.QueryContext(ctx, query, pq.Array(scopes))
	if err != nil {
		return nil, fmt.Errorf("failed to validate permission scopes: %w", err)
	}
	defer rows.Close()

	validScopes := make(map[string]bool)
	for rows.Next() {
		var scope string
		if err := rows.Scan(&scope); err != nil {
			return nil, fmt.Errorf("failed to scan scope: %w", err)
		}
		validScopes[scope] = true
	}

	if err = rows.Err(); err != nil {
		return nil, fmt.Errorf("error iterating scopes: %w", err)
	}

	var result []string
	for _, scope := range scopes {
		if validScopes[scope] {
			result = append(result, scope)
		}
	}

	return result, nil
}

// GetPermissionHierarchy returns all parent and child permissions for given scopes
func (r *PermissionRepository) GetPermissionHierarchy(ctx context.Context, scopes []string) ([]*domain.AvailablePermission, error) {
	if len(scopes) == 0 {
		return []*domain.AvailablePermission{}, nil
	}

	// Use recursive CTE to get full hierarchy
	query := `
		WITH RECURSIVE permission_hierarchy AS (
			-- Base case: get permissions matching the input scopes
			SELECT id, scope, name, description, category, parent_scope, 
			       is_system, created_at, created_by, updated_at, updated_by, 0 as level
			FROM available_permissions 
			WHERE scope = ANY($1)
			
			UNION ALL
			
			-- Recursive case: get all parents and children
			SELECT ap.id, ap.scope, ap.name, ap.description, ap.category, ap.parent_scope,
			       ap.is_system, ap.created_at, ap.created_by, ap.updated_at, ap.updated_by,
			       ph.level + 1 as level
			FROM available_permissions ap
			JOIN permission_hierarchy ph ON (
				-- Get parents (where ap.scope = ph.parent_scope)
				ap.scope = ph.parent_scope 
				OR 
				-- Get children (where ap.parent_scope = ph.scope)
				ap.parent_scope = ph.scope
			)
			WHERE ph.level < 5  -- Prevent infinite recursion
		)
		SELECT DISTINCT id, scope, name, description, category, parent_scope,
		       is_system, created_at, created_by, updated_at, updated_by
		FROM permission_hierarchy
		ORDER BY scope
	`

	db := r.db.GetDB().(*sql.DB)
	rows, err := db.QueryContext(ctx, query, pq.Array(scopes))
	if err != nil {
		return nil, fmt.Errorf("failed to get permission hierarchy: %w", err)
	}
	defer rows.Close()

	var permissions []*domain.AvailablePermission
	for rows.Next() {
		permission := &domain.AvailablePermission{}
		err := rows.Scan(
			&permission.ID,
			&permission.Scope,
			&permission.Name,
			&permission.Description,
			&permission.Category,
			&permission.ParentScope,
			&permission.IsSystem,
			&permission.CreatedAt,
			&permission.CreatedBy,
			&permission.UpdatedAt,
			&permission.UpdatedBy,
		)
		if err != nil {
			return nil, fmt.Errorf("failed to scan permission hierarchy: %w", err)
		}
		permissions = append(permissions, permission)
	}

	if err = rows.Err(); err != nil {
		return nil, fmt.Errorf("failed to iterate permission hierarchy: %w", err)
	}

	return permissions, nil
}

// GrantedPermissionRepository implements the GrantedPermissionRepository interface for PostgreSQL
type GrantedPermissionRepository struct {
	db repository.DatabaseProvider
}

// NewGrantedPermissionRepository creates a new PostgreSQL granted permission repository
func NewGrantedPermissionRepository(db repository.DatabaseProvider) repository.GrantedPermissionRepository {
	return &GrantedPermissionRepository{db: db}
}

// GrantPermissions grants multiple permissions to a token
func (r *GrantedPermissionRepository) GrantPermissions(ctx context.Context, grants []*domain.GrantedPermission) error {
	if len(grants) == 0 {
		return nil
	}

	db := r.db.GetDB().(*sql.DB)
	tx, err := db.BeginTx(ctx, nil)
	if err != nil {
		return fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer tx.Rollback()

	query := `
		INSERT INTO granted_permissions (
			id, token_type, token_id, permission_id, scope, created_by, created_at
		) VALUES ($1, $2, $3, $4, $5, $6, $7)
		ON CONFLICT (token_type, token_id, permission_id) DO NOTHING
	`

	stmt, err := tx.PrepareContext(ctx, query)
	if err != nil {
		return fmt.Errorf("failed to prepare statement: %w", err)
	}
	defer stmt.Close()

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

		_, err = stmt.ExecContext(ctx,
			grant.ID,
			string(grant.TokenType),
			grant.TokenID,
			grant.PermissionID,
			grant.Scope,
			grant.CreatedBy,
			now,
		)
		if err != nil {
			return fmt.Errorf("failed to grant permission: %w", err)
		}

		grant.CreatedAt = now
	}

	if err = tx.Commit(); err != nil {
		return fmt.Errorf("failed to commit transaction: %w", err)
	}

	return nil
}

// GetGrantedPermissions retrieves all granted permissions for a token
func (r *GrantedPermissionRepository) GetGrantedPermissions(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID) ([]*domain.GrantedPermission, error) {
	query := `
		SELECT id, token_type, token_id, permission_id, scope, created_at, created_by, revoked
		FROM granted_permissions 
		WHERE token_type = $1 AND token_id = $2 AND revoked = false
		ORDER BY created_at ASC
	`

	db := r.db.GetDB().(*sql.DB)
	rows, err := db.QueryContext(ctx, query, string(tokenType), tokenID)
	if err != nil {
		return nil, fmt.Errorf("failed to query granted permissions: %w", err)
	}
	defer rows.Close()

	var permissions []*domain.GrantedPermission
	for rows.Next() {
		perm := &domain.GrantedPermission{}
		var tokenTypeStr string

		err := rows.Scan(
			&perm.ID,
			&tokenTypeStr,
			&perm.TokenID,
			&perm.PermissionID,
			&perm.Scope,
			&perm.CreatedAt,
			&perm.CreatedBy,
			&perm.Revoked,
		)

		if err != nil {
			return nil, fmt.Errorf("failed to scan granted permission: %w", err)
		}

		perm.TokenType = domain.TokenType(tokenTypeStr)
		permissions = append(permissions, perm)
	}

	if err = rows.Err(); err != nil {
		return nil, fmt.Errorf("error iterating granted permissions: %w", err)
	}

	return permissions, nil
}

// GetGrantedPermissionScopes retrieves only the scopes for a token (more efficient)
func (r *GrantedPermissionRepository) GetGrantedPermissionScopes(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID) ([]string, error) {
	query := `
		SELECT scope
		FROM granted_permissions 
		WHERE token_type = $1 AND token_id = $2 AND revoked = false
		ORDER BY scope ASC
	`

	db := r.db.GetDB().(*sql.DB)
	rows, err := db.QueryContext(ctx, query, string(tokenType), tokenID)
	if err != nil {
		return nil, fmt.Errorf("failed to query granted permission scopes: %w", err)
	}
	defer rows.Close()

	var scopes []string
	for rows.Next() {
		var scope string
		if err := rows.Scan(&scope); err != nil {
			return nil, fmt.Errorf("failed to scan permission scope: %w", err)
		}
		scopes = append(scopes, scope)
	}

	if err = rows.Err(); err != nil {
		return nil, fmt.Errorf("error iterating permission scopes: %w", err)
	}

	return scopes, nil
}

// RevokePermission revokes a specific permission from a token
func (r *GrantedPermissionRepository) RevokePermission(ctx context.Context, grantID uuid.UUID, revokedBy string) error {
	query := `
		UPDATE granted_permissions 
		SET revoked = true, revoked_by = $2, revoked_at = $3
		WHERE id = $1 AND revoked = false
	`

	db := r.db.GetDB().(*sql.DB)
	now := time.Now()

	result, err := db.ExecContext(ctx, query, grantID, revokedBy, now)
	if err != nil {
		return fmt.Errorf("failed to revoke permission: %w", err)
	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("failed to get rows affected: %w", err)
	}

	if rowsAffected == 0 {
		return fmt.Errorf("permission grant with ID %s not found or already revoked", grantID)
	}

	return nil
}

// RevokeAllPermissions revokes all permissions from a token
func (r *GrantedPermissionRepository) RevokeAllPermissions(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID, revokedBy string) error {
	query := `
		UPDATE granted_permissions 
		SET revoked = true, revoked_by = $3, revoked_at = $4
		WHERE token_type = $1 AND token_id = $2 AND revoked = false
	`

	db := r.db.GetDB().(*sql.DB)
	now := time.Now()

	result, err := db.ExecContext(ctx, query, tokenType, tokenID, revokedBy, now)
	if err != nil {
		return fmt.Errorf("failed to revoke all permissions: %w", err)
	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("failed to get rows affected: %w", err)
	}

	// Note: rowsAffected being 0 is not necessarily an error here - 
	// the token might not have had any active permissions
	_ = rowsAffected

	return nil
}

// HasPermission checks if a token has a specific permission
func (r *GrantedPermissionRepository) HasPermission(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID, scope string) (bool, error) {
	query := `
		SELECT 1 
		FROM granted_permissions gp
		JOIN available_permissions ap ON gp.permission_id = ap.id
		WHERE gp.token_type = $1 
		  AND gp.token_id = $2 
		  AND gp.scope = $3 
		  AND gp.revoked = false
		LIMIT 1
	`

	db := r.db.GetDB().(*sql.DB)
	var exists int
	err := db.QueryRowContext(ctx, query, string(tokenType), tokenID, scope).Scan(&exists)

	if err != nil {
		if err == sql.ErrNoRows {
			return false, nil
		}
		return false, fmt.Errorf("failed to check permission: %w", err)
	}

	return true, nil
}

// HasAnyPermission checks if a token has any of the specified permissions
func (r *GrantedPermissionRepository) HasAnyPermission(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID, scopes []string) (map[string]bool, error) {
	if len(scopes) == 0 {
		return make(map[string]bool), nil
	}

	query := `
		SELECT gp.scope
		FROM granted_permissions gp
		JOIN available_permissions ap ON gp.permission_id = ap.id
		WHERE gp.token_type = $1 
		  AND gp.token_id = $2 
		  AND gp.scope = ANY($3)
		  AND gp.revoked = false
	`

	db := r.db.GetDB().(*sql.DB)
	rows, err := db.QueryContext(ctx, query, string(tokenType), tokenID, pq.Array(scopes))
	if err != nil {
		return nil, fmt.Errorf("failed to check permissions: %w", err)
	}
	defer rows.Close()

	result := make(map[string]bool)
	// Initialize all scopes as false
	for _, scope := range scopes {
		result[scope] = false
	}

	// Mark found permissions as true
	for rows.Next() {
		var scope string
		if err := rows.Scan(&scope); err != nil {
			return nil, fmt.Errorf("failed to scan permission scope: %w", err)
		}
		result[scope] = true
	}

	if err = rows.Err(); err != nil {
		return nil, fmt.Errorf("error iterating permission results: %w", err)
	}

	return result, nil
}
