package db

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

type CreateUserParams struct {
	Email        string
	PasswordHash string
	Role         UserRole
	IsActive     bool
}

func (r *UserRepository) HasActiveAdmin(ctx context.Context) (bool, error) {
	var exists int
	if err := r.q.QueryRowContext(
		ctx,
		`SELECT EXISTS(SELECT 1 FROM users WHERE role = ? AND is_active = 1 LIMIT 1)`,
		UserRoleAdmin,
	).Scan(&exists); err != nil {
		return false, fmt.Errorf("query active admin existence: %w", err)
	}

	return exists == 1, nil
}

func (r *UserRepository) GetByID(ctx context.Context, id int64) (*User, error) {
	user, err := scanUser(r.q.QueryRowContext(
		ctx,
		`SELECT id, email, password_hash, role, is_active, created_at, updated_at, last_login_at
		FROM users
		WHERE id = ?`,
		id,
	))
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, ErrNotFound
		}

		return nil, fmt.Errorf("scan user by id: %w", err)
	}

	return user, nil
}

func (r *UserRepository) GetByEmail(ctx context.Context, email string) (*User, error) {
	user, err := scanUser(r.q.QueryRowContext(
		ctx,
		`SELECT id, email, password_hash, role, is_active, created_at, updated_at, last_login_at
		FROM users
		WHERE email = ? COLLATE NOCASE
		LIMIT 1`,
		strings.TrimSpace(email),
	))
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, ErrNotFound
		}

		return nil, fmt.Errorf("scan user by email: %w", err)
	}

	return user, nil
}

func (r *UserRepository) Create(ctx context.Context, params CreateUserParams) (*User, error) {
	isActive := 0
	if params.IsActive {
		isActive = 1
	}

	result, err := r.q.ExecContext(
		ctx,
		`INSERT INTO users (email, password_hash, role, is_active) VALUES (?, ?, ?, ?)`,
		strings.TrimSpace(params.Email),
		params.PasswordHash,
		params.Role,
		isActive,
	)
	if err != nil {
		return nil, fmt.Errorf("insert user: %w", err)
	}

	userID, err := result.LastInsertId()
	if err != nil {
		return nil, fmt.Errorf("load inserted user id: %w", err)
	}

	return r.GetByID(ctx, userID)
}

func (r *UserRepository) UpdateLastLoginAt(ctx context.Context, userID int64, loggedInAt time.Time) error {
	result, err := r.q.ExecContext(
		ctx,
		`UPDATE users SET last_login_at = ? WHERE id = ?`,
		formatTimestamp(loggedInAt),
		userID,
	)
	if err != nil {
		return fmt.Errorf("update user last_login_at: %w", err)
	}

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

	if rowsAffected == 0 {
		return ErrNotFound
	}

	return nil
}

func scanUser(scanner rowScanner) (*User, error) {
	var (
		user         User
		role         string
		isActive     int
		createdAtRaw string
		updatedAtRaw string
		lastLoginRaw sql.NullString
	)

	if err := scanner.Scan(
		&user.ID,
		&user.Email,
		&user.PasswordHash,
		&role,
		&isActive,
		&createdAtRaw,
		&updatedAtRaw,
		&lastLoginRaw,
	); err != nil {
		return nil, err
	}

	createdAt, err := parseTimestamp(createdAtRaw)
	if err != nil {
		return nil, fmt.Errorf("parse user created_at: %w", err)
	}

	updatedAt, err := parseTimestamp(updatedAtRaw)
	if err != nil {
		return nil, fmt.Errorf("parse user updated_at: %w", err)
	}

	lastLoginAt, err := parseNullableTimestamp(lastLoginRaw)
	if err != nil {
		return nil, fmt.Errorf("parse user last_login_at: %w", err)
	}

	user.Role = UserRole(role)
	user.IsActive = isActive == 1
	user.CreatedAt = createdAt
	user.UpdatedAt = updatedAt
	user.LastLoginAt = lastLoginAt

	return &user, nil
}
