package db

import (
	"context"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgconn"
	"github.com/jackc/pgx/v5/pgxpool"
)

// Querier is implemented by *pgxpool.Pool, pgx.Tx, and *PoolProxy.
// Fixture functions and internal helpers that need to run queries should
// accept Querier to remain decoupled from transaction state.
//
// DISTINCTION: QueryRow vs Querier
// - QueryRow() (lowercase 'r') is a METHOD on Querier. It returns pgx.Row.
//   Call it like: row := q.QueryRow(ctx, sql, args...)
// - Querier (uppercase 'Q') is an INTERFACE. It declares the QueryRow method.
//   Accept Querier when a helper must work with both pool + transactions.
// - pgx.Row (singular) is the RETURN TYPE of QueryRow(). It is NOT a Querier.
//   pgx.Row only has Scan(). You cannot pass a pgx.Row where Querier is expected.
//
// Common mistake:
//   // WRONG — pgx.Row does not implement Querier:
//   func helper(ctx, row pgx.Row) { ... }
//   // CORRECT — accept Querier, call QueryRow inside:
//   func helper(ctx, q db.Querier) { row := q.QueryRow(ctx, sql, args...); ... }
type Querier interface {
	Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error)
	Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
	QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
}

// PoolProxy wraps *pgxpool.Pool and routes DB operations through an active
// transaction stored in context.Context. If no transaction is present,
// it delegates to the underlying pool directly.
//
// This enables per-test transactions: tests store a pgx.Tx in the request
// context, and all handler DB calls (via db.Conn.Exec/Query/QueryRow) route
// through that transaction automatically, rolling back on test cleanup.
type PoolProxy struct {
	pool *pgxpool.Pool
}

// NewPoolProxy creates a PoolProxy wrapping the given pool.
func NewPoolProxy(pool *pgxpool.Pool) *PoolProxy {
	return &PoolProxy{pool: pool}
}

// Pool returns the underlying pool, used for test setup and startup code.
func (p *PoolProxy) Pool() *pgxpool.Pool { return p.pool }

// Exec runs a query, routing through a context transaction if one is active.
func (p *PoolProxy) Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error) {
	if tx := TxFromContext(ctx); tx != nil {
		return tx.Exec(ctx, sql, args...)
	}
	return p.pool.Exec(ctx, sql, args...)
}

// Query runs a query, routing through a context transaction if one is active.
func (p *PoolProxy) Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error) {
	if tx := TxFromContext(ctx); tx != nil {
		return tx.Query(ctx, sql, args...)
	}
	return p.pool.Query(ctx, sql, args...)
}

// QueryRow runs a query, routing through a context transaction if one is active.
func (p *PoolProxy) QueryRow(ctx context.Context, sql string, args ...any) pgx.Row {
	if tx := TxFromContext(ctx); tx != nil {
		return tx.QueryRow(ctx, sql, args...)
	}
	return p.pool.QueryRow(ctx, sql, args...)
}

// Begin starts a transaction. If the context already has an active transaction,
// it creates a savepoint (nested transaction) instead. This allows production
// code that calls db.Conn.Begin() to work inside per-test transactions.
func (p *PoolProxy) Begin(ctx context.Context) (pgx.Tx, error) {
	if tx := TxFromContext(ctx); tx != nil {
		return tx.Begin(ctx)
	}
	return p.pool.Begin(ctx)
}

// Ping always goes to the underlying pool — it's a health check, not a query.
func (p *PoolProxy) Ping(ctx context.Context) error {
	return p.pool.Ping(ctx)
}

// Acquire always goes to the underlying pool — pgx.Tx has no Acquire method.
// The only production caller is the payment advisory lock, which needs a
// dedicated connection orthogonal to any transaction context.
func (p *PoolProxy) Acquire(ctx context.Context) (*pgxpool.Conn, error) {
	return p.pool.Acquire(ctx)
}
