// Package postgres provides PostgreSQL-based implementations of port interfaces.
package postgres

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

	"github.com/lib/pq"
	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/port"
)

// APIKeyRepository implements port.APIKeyRepository using PostgreSQL.
type APIKeyRepository struct {
	db *sql.DB
}

// NewAPIKeyRepository creates a new PostgreSQL API key repository.
func NewAPIKeyRepository(db *sql.DB) *APIKeyRepository {
	return &APIKeyRepository{db: db}
}

// Ensure APIKeyRepository implements port.APIKeyRepository at compile time.
var _ port.APIKeyRepository = (*APIKeyRepository)(nil)

// Create stores a new API key.
func (r *APIKeyRepository) Create(ctx context.Context, key *domain.APIKey, keyHash string) error {
	scopeStrings := scopesToStrings(key.Scopes)
	projectIDStrings := projectIDsToStrings(key.ProjectIDs)

	var id string
	err := r.db.QueryRowContext(ctx, `
		INSERT INTO api_keys (name, key_hash, key_prefix, scopes, project_ids, allowed_ips, expires_at, created_by)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
		RETURNING id
	`, key.Name, keyHash, key.KeyPrefix, pq.Array(scopeStrings), pq.Array(projectIDStrings), pq.Array(key.AllowedIPs), key.ExpiresAt, key.CreatedBy).Scan(&id)

	if err != nil {
		return fmt.Errorf("insert key: %w", err)
	}

	key.ID = domain.APIKeyID(id)
	key.CreatedAt = time.Now()
	return nil
}

// GetByHash retrieves an API key by its hash.
func (r *APIKeyRepository) GetByHash(ctx context.Context, keyHash string) (*domain.APIKey, error) {
	var (
		key          domain.APIKey
		id           string
		scopeStrings []string
		projectIDs   []string
	)

	err := r.db.QueryRowContext(ctx, `
		SELECT id, name, key_prefix, scopes, project_ids, allowed_ips, created_at, expires_at, last_used_at, revoked_at, created_by
		FROM api_keys
		WHERE key_hash = $1
	`, keyHash).Scan(
		&id,
		&key.Name,
		&key.KeyPrefix,
		pq.Array(&scopeStrings),
		pq.Array(&projectIDs),
		pq.Array(&key.AllowedIPs),
		&key.CreatedAt,
		&key.ExpiresAt,
		&key.LastUsedAt,
		&key.RevokedAt,
		&key.CreatedBy,
	)

	if errors.Is(err, sql.ErrNoRows) {
		return nil, domain.ErrKeyNotFound
	}
	if err != nil {
		return nil, fmt.Errorf("query key: %w", err)
	}

	key.ID = domain.APIKeyID(id)
	key.Scopes = scopesFromStrings(scopeStrings)
	key.ProjectIDs = projectIDsFromStrings(projectIDs)

	return &key, nil
}

// Get retrieves an API key by ID.
func (r *APIKeyRepository) Get(ctx context.Context, id domain.APIKeyID) (*domain.APIKey, error) {
	var (
		key          domain.APIKey
		keyID        string
		scopeStrings []string
		projectIDs   []string
	)

	err := r.db.QueryRowContext(ctx, `
		SELECT id, name, key_prefix, scopes, project_ids, allowed_ips, created_at, expires_at, last_used_at, revoked_at, created_by
		FROM api_keys
		WHERE id = $1
	`, string(id)).Scan(
		&keyID,
		&key.Name,
		&key.KeyPrefix,
		pq.Array(&scopeStrings),
		pq.Array(&projectIDs),
		pq.Array(&key.AllowedIPs),
		&key.CreatedAt,
		&key.ExpiresAt,
		&key.LastUsedAt,
		&key.RevokedAt,
		&key.CreatedBy,
	)

	if errors.Is(err, sql.ErrNoRows) {
		return nil, domain.ErrKeyNotFound
	}
	if err != nil {
		return nil, fmt.Errorf("query key: %w", err)
	}

	key.ID = domain.APIKeyID(keyID)
	key.Scopes = scopesFromStrings(scopeStrings)
	key.ProjectIDs = projectIDsFromStrings(projectIDs)

	return &key, nil
}

// List returns all API keys (without secrets).
func (r *APIKeyRepository) List(ctx context.Context) ([]*domain.APIKey, error) {
	rows, err := r.db.QueryContext(ctx, `
		SELECT id, name, key_prefix, scopes, project_ids, allowed_ips, created_at, expires_at, last_used_at, revoked_at, created_by
		FROM api_keys
		ORDER BY created_at DESC
	`)
	if err != nil {
		return nil, fmt.Errorf("query keys: %w", err)
	}
	defer func() { _ = rows.Close() }()

	var keys []*domain.APIKey
	for rows.Next() {
		var (
			key          domain.APIKey
			id           string
			scopeStrings []string
			projectIDs   []string
		)
		if err := rows.Scan(
			&id,
			&key.Name,
			&key.KeyPrefix,
			pq.Array(&scopeStrings),
			pq.Array(&projectIDs),
			pq.Array(&key.AllowedIPs),
			&key.CreatedAt,
			&key.ExpiresAt,
			&key.LastUsedAt,
			&key.RevokedAt,
			&key.CreatedBy,
		); err != nil {
			return nil, fmt.Errorf("scan key: %w", err)
		}
		key.ID = domain.APIKeyID(id)
		key.Scopes = scopesFromStrings(scopeStrings)
		key.ProjectIDs = projectIDsFromStrings(projectIDs)
		keys = append(keys, &key)
	}

	return keys, nil
}

// Revoke marks an API key as revoked.
func (r *APIKeyRepository) Revoke(ctx context.Context, id domain.APIKeyID) error {
	result, err := r.db.ExecContext(ctx, `
		UPDATE api_keys SET revoked_at = NOW()
		WHERE id = $1 AND revoked_at IS NULL
	`, string(id))
	if err != nil {
		return fmt.Errorf("revoke key: %w", err)
	}

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

	if rows == 0 {
		return domain.ErrKeyNotFound
	}

	return nil
}

// UpdateLastUsed updates the last used timestamp for a key.
func (r *APIKeyRepository) UpdateLastUsed(ctx context.Context, id domain.APIKeyID) error {
	_, err := r.db.ExecContext(ctx, `
		UPDATE api_keys SET last_used_at = NOW() WHERE id = $1
	`, string(id))
	return err
}

// Helper functions for scope conversion
func scopesToStrings(scopes []domain.Scope) []string {
	ss := make([]string, len(scopes))
	for i, s := range scopes {
		ss[i] = string(s)
	}
	return ss
}

func scopesFromStrings(ss []string) []domain.Scope {
	scopes := make([]domain.Scope, len(ss))
	for i, s := range ss {
		scopes[i] = domain.Scope(s)
	}
	return scopes
}

func projectIDsToStrings(ids []domain.ProjectID) []string {
	if ids == nil {
		return nil
	}
	ss := make([]string, len(ids))
	for i, id := range ids {
		ss[i] = string(id)
	}
	return ss
}

func projectIDsFromStrings(ss []string) []domain.ProjectID {
	if ss == nil {
		return nil
	}
	ids := make([]domain.ProjectID, len(ss))
	for i, s := range ss {
		ids[i] = domain.ProjectID(s)
	}
	return ids
}
