package repository

import (
	"context"
	"errors"
	"time"

	"github.com/google/uuid"
	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"
)

type PasswordResetToken struct {
	ID                  uuid.UUID
	ClientUserID        uuid.UUID
	Email               string
	OTPCode             string
	OTPExpiresAt        time.Time
	ResetToken          *string
	ResetTokenExpiresAt *time.Time
	IsUsed              bool
	CreatedAt           time.Time
	UpdatedAt           time.Time
}

type PasswordResetRepository struct {
	db *pgxpool.Pool
}

func NewPasswordResetRepository(db *pgxpool.Pool) *PasswordResetRepository {
	return &PasswordResetRepository{db: db}
}

// CreateOTP creates a new OTP record
func (r *PasswordResetRepository) CreateOTP(ctx context.Context, clientUserID uuid.UUID, email, otpCode string, expiresAt time.Time) error {
	query := `
		INSERT INTO password_reset_tokens (client_user_id, email, otp_code, otp_expires_at)
		VALUES ($1, $2, $3, $4)
	`
	_, err := r.db.Exec(ctx, query, clientUserID, email, otpCode, expiresAt)
	return err
}

// FindByEmailAndOTP finds a token by email and OTP
func (r *PasswordResetRepository) FindByEmailAndOTP(ctx context.Context, email, otpCode string) (*PasswordResetToken, error) {
	query := `
		SELECT id, client_user_id, email, otp_code, otp_expires_at, reset_token, reset_token_expires_at, is_used, created_at, updated_at
		FROM password_reset_tokens
		WHERE email = $1 AND otp_code = $2 AND is_used = FALSE
		ORDER BY created_at DESC
		LIMIT 1
	`

	var token PasswordResetToken
	err := r.db.QueryRow(ctx, query, email, otpCode).Scan(
		&token.ID,
		&token.ClientUserID,
		&token.Email,
		&token.OTPCode,
		&token.OTPExpiresAt,
		&token.ResetToken,
		&token.ResetTokenExpiresAt,
		&token.IsUsed,
		&token.CreatedAt,
		&token.UpdatedAt,
	)

	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, errors.New("invalid OTP code")
		}
		return nil, err
	}

	return &token, nil
}

// UpdateWithResetToken updates the token with a reset token after OTP verification
func (r *PasswordResetRepository) UpdateWithResetToken(ctx context.Context, id uuid.UUID, resetToken string, expiresAt time.Time) error {
	query := `
		UPDATE password_reset_tokens
		SET reset_token = $2, reset_token_expires_at = $3, updated_at = CURRENT_TIMESTAMP
		WHERE id = $1
	`
	_, err := r.db.Exec(ctx, query, id, resetToken, expiresAt)
	return err
}

// FindByResetToken finds a token by reset token
func (r *PasswordResetRepository) FindByResetToken(ctx context.Context, resetToken string) (*PasswordResetToken, error) {
	query := `
		SELECT id, client_user_id, email, otp_code, otp_expires_at, reset_token, reset_token_expires_at, is_used, created_at, updated_at
		FROM password_reset_tokens
		WHERE reset_token = $1 AND is_used = FALSE
		LIMIT 1
	`

	var token PasswordResetToken
	err := r.db.QueryRow(ctx, query, resetToken).Scan(
		&token.ID,
		&token.ClientUserID,
		&token.Email,
		&token.OTPCode,
		&token.OTPExpiresAt,
		&token.ResetToken,
		&token.ResetTokenExpiresAt,
		&token.IsUsed,
		&token.CreatedAt,
		&token.UpdatedAt,
	)

	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, errors.New("invalid reset token")
		}
		return nil, err
	}

	return &token, nil
}

// MarkAsUsed marks a token as used
func (r *PasswordResetRepository) MarkAsUsed(ctx context.Context, id uuid.UUID) error {
	query := `
		UPDATE password_reset_tokens
		SET is_used = TRUE, updated_at = CURRENT_TIMESTAMP
		WHERE id = $1
	`
	_, err := r.db.Exec(ctx, query, id)
	return err
}

// InvalidatePreviousTokens invalidates previous unused tokens for a user
func (r *PasswordResetRepository) InvalidatePreviousTokens(ctx context.Context, clientUserID uuid.UUID) error {
	query := `
		UPDATE password_reset_tokens
		SET is_used = TRUE, updated_at = CURRENT_TIMESTAMP
		WHERE client_user_id = $1 AND is_used = FALSE
	`
	_, err := r.db.Exec(ctx, query, clientUserID)
	return err
}

