package postgres

import (
	"context"
	"errors"
	"strings"

	"github.com/google/uuid"
	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/tdvorak/seen/backend/internal/domain"
)

var ErrUserAlreadyExists = errors.New("user already exists")

type AuthRepository struct {
	pool *pgxpool.Pool
}

func NewAuthRepository(pool *pgxpool.Pool) *AuthRepository {
	return &AuthRepository{pool: pool}
}

func (r *AuthRepository) CreateUser(ctx context.Context, user domain.User) error {
	_, err := r.pool.Exec(
		ctx,
		`INSERT INTO users (id, email, display_name, role, password_hash, created_at, updated_at)
		 VALUES ($1, $2, $3, $4, $5, $6, $7)`,
		user.ID,
		strings.ToLower(strings.TrimSpace(user.Email)),
		user.DisplayName,
		user.Role,
		user.PasswordHash,
		user.CreatedAt,
		user.UpdatedAt,
	)
	if err != nil && strings.Contains(err.Error(), "duplicate key") {
		return ErrUserAlreadyExists
	}

	return err
}

func (r *AuthRepository) FindUserByEmail(ctx context.Context, email string) (*domain.User, error) {
	row := r.pool.QueryRow(
		ctx,
		`SELECT id, email, display_name, role, password_hash, created_at, updated_at
		 FROM users
		 WHERE email = $1`,
		strings.ToLower(strings.TrimSpace(email)),
	)

	var user domain.User
	if err := row.Scan(
		&user.ID,
		&user.Email,
		&user.DisplayName,
		&user.Role,
		&user.PasswordHash,
		&user.CreatedAt,
		&user.UpdatedAt,
	); err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, nil
		}
		return nil, err
	}

	return &user, nil
}

func (r *AuthRepository) FindUserByID(ctx context.Context, userID uuid.UUID) (*domain.User, error) {
	row := r.pool.QueryRow(
		ctx,
		`SELECT id, email, display_name, role, password_hash, created_at, updated_at
		 FROM users
		 WHERE id = $1`,
		userID,
	)

	var user domain.User
	if err := row.Scan(
		&user.ID,
		&user.Email,
		&user.DisplayName,
		&user.Role,
		&user.PasswordHash,
		&user.CreatedAt,
		&user.UpdatedAt,
	); err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, nil
		}
		return nil, err
	}

	return &user, nil
}

func (r *AuthRepository) CreateSession(ctx context.Context, session domain.Session) error {
	_, err := r.pool.Exec(
		ctx,
		`INSERT INTO sessions (id, user_id, refresh_token, user_agent, ip, expires_at, created_at)
		 VALUES ($1, $2, $3, $4, $5, $6, $7)`,
		session.ID,
		session.UserID,
		session.RefreshToken,
		session.UserAgent,
		session.IP,
		session.ExpiresAt,
		session.CreatedAt,
	)
	return err
}

func (r *AuthRepository) FindSessionByRefreshToken(ctx context.Context, refreshToken string) (*domain.Session, error) {
	row := r.pool.QueryRow(
		ctx,
		`SELECT id, user_id, refresh_token, user_agent, ip, expires_at, revoked_at, created_at
		 FROM sessions
		 WHERE refresh_token = $1`,
		refreshToken,
	)

	var session domain.Session
	if err := row.Scan(
		&session.ID,
		&session.UserID,
		&session.RefreshToken,
		&session.UserAgent,
		&session.IP,
		&session.ExpiresAt,
		&session.RevokedAt,
		&session.CreatedAt,
	); err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return nil, nil
		}
		return nil, err
	}

	return &session, nil
}

func (r *AuthRepository) RevokeSession(ctx context.Context, sessionID uuid.UUID) error {
	_, err := r.pool.Exec(
		ctx,
		`UPDATE sessions
		 SET revoked_at = now()
		 WHERE id = $1`,
		sessionID,
	)
	return err
}
