package postgres

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

	"github.com/jmoiron/sqlx"

	"{{GO_MODULE}}/services/{{COMPONENT_NAME}}/internal/domain"
	"{{GO_MODULE}}/services/{{COMPONENT_NAME}}/internal/port"
)

// Compile-time interface check.
var _ port.AuthCodeRepository = (*AuthCodeRepository)(nil)

// authCodeRow maps to the auth_codes table.
type authCodeRow struct {
	ID        string     `db:"id"`
	UserID    *string    `db:"user_id"`
	Email     string     `db:"email"`
	Code      string     `db:"code"`
	Purpose   string     `db:"purpose"`
	ExpiresAt time.Time  `db:"expires_at"`
	UsedAt    *time.Time `db:"used_at"`
	IPAddress string     `db:"ip_address"`
	CreatedAt time.Time  `db:"created_at"`
}

func (r *authCodeRow) toDomain() *domain.AuthCode {
	ac := &domain.AuthCode{
		ID:        r.ID,
		Email:     r.Email,
		Code:      r.Code,
		Purpose:   domain.AuthCodePurpose(r.Purpose),
		ExpiresAt: r.ExpiresAt,
		UsedAt:    r.UsedAt,
		IPAddress: r.IPAddress,
		CreatedAt: r.CreatedAt,
	}
	if r.UserID != nil {
		uid := domain.UserID(*r.UserID)
		ac.UserID = &uid
	}
	return ac
}

// AuthCodeRepository implements port.AuthCodeRepository with PostgreSQL/CockroachDB.
type AuthCodeRepository struct {
	db *sqlx.DB
}

// NewAuthCodeRepository creates a new Postgres-backed auth code repository.
func NewAuthCodeRepository(db *sqlx.DB) *AuthCodeRepository {
	return &AuthCodeRepository{db: db}
}

func (r *AuthCodeRepository) Create(ctx context.Context, code *domain.AuthCode) error {
	var userID *string
	if code.UserID != nil {
		s := string(*code.UserID)
		userID = &s
	}

	_, err := r.db.ExecContext(ctx, `
		INSERT INTO auth_codes (id, user_id, email, code, purpose, expires_at, ip_address, created_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
	`, code.ID, userID, code.Email, code.Code, string(code.Purpose),
		code.ExpiresAt, code.IPAddress, code.CreatedAt)
	if err != nil {
		return fmt.Errorf("insert auth code: %w", err)
	}
	return nil
}

func (r *AuthCodeRepository) FindValid(ctx context.Context, email string, code string, purpose domain.AuthCodePurpose) (*domain.AuthCode, error) {
	var row authCodeRow
	err := r.db.GetContext(ctx, &row, `
		SELECT id, user_id, email, code, purpose, expires_at, used_at, ip_address, created_at
		FROM auth_codes
		WHERE email = $1 AND code = $2 AND purpose = $3
		  AND used_at IS NULL AND expires_at > NOW()
		ORDER BY created_at DESC
		LIMIT 1
	`, email, code, string(purpose))
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, domain.ErrInvalidAuthCode
		}
		return nil, fmt.Errorf("find valid auth code: %w", err)
	}
	return row.toDomain(), nil
}

func (r *AuthCodeRepository) MarkUsed(ctx context.Context, id string) error {
	_, err := r.db.ExecContext(ctx, `
		UPDATE auth_codes SET used_at = NOW() WHERE id = $1
	`, id)
	if err != nil {
		return fmt.Errorf("mark auth code used: %w", err)
	}
	return nil
}

func (r *AuthCodeRepository) DeleteExpired(ctx context.Context) (int, error) {
	result, err := r.db.ExecContext(ctx, `
		DELETE FROM auth_codes WHERE expires_at < NOW()
	`)
	if err != nil {
		return 0, fmt.Errorf("delete expired auth codes: %w", err)
	}

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