<?php

declare(strict_types=1);

namespace BotGuard\Core;

/**
 * Authentication service.
 *
 * Handles credential verification (argon2id/bcrypt via password_hash), login
 * state in the session, and role checks. Kept independent of the User model so
 * it can be used during install before models are wired up.
 */
final class Auth
{
    private const SESSION_KEY = '__auth_user_id';

    /** @var array<string, mixed>|null In-request cache of the authenticated user row. */
    private ?array $user = null;

    private bool $resolved = false;

    public function __construct(private Session $session, private Database $db)
    {
    }

    /**
     * Attempt to authenticate with an email/password pair.
     */
    public function attempt(string $email, string $password): bool
    {
        $user = $this->db->selectOne(
            'SELECT * FROM `users` WHERE `email` = ? AND `status` = ? LIMIT 1',
            [strtolower(trim($email)), 'active']
        );

        if ($user === null || ! password_verify($password, (string) $user['password'])) {
            return false;
        }

        // Transparently upgrade legacy hashes if the algorithm/cost changed.
        if (password_needs_rehash((string) $user['password'], PASSWORD_DEFAULT)) {
            $this->db->update('users', ['password' => self::hash($password)], ['id' => $user['id']]);
        }

        $this->login((int) $user['id']);

        return true;
    }

    /**
     * Mark a user id as logged in and rotate the session id.
     */
    public function login(int $userId): void
    {
        $this->session->regenerate();
        $this->session->put(self::SESSION_KEY, $userId);
        $this->user = null;
        $this->resolved = false;
    }

    public function logout(): void
    {
        $this->session->forget(self::SESSION_KEY);
        $this->user = null;
        $this->resolved = false;
    }

    public function check(): bool
    {
        return $this->id() !== null;
    }

    public function guest(): bool
    {
        return ! $this->check();
    }

    public function id(): ?int
    {
        $id = $this->session->get(self::SESSION_KEY);

        return is_numeric($id) ? (int) $id : null;
    }

    /**
     * The authenticated user row, or null.
     *
     * @return array<string, mixed>|null
     */
    public function user(): ?array
    {
        if ($this->resolved) {
            return $this->user;
        }

        $this->resolved = true;
        $id = $this->id();

        if ($id === null) {
            return $this->user = null;
        }

        return $this->user = $this->db->selectOne(
            'SELECT * FROM `users` WHERE `id` = ? LIMIT 1',
            [$id]
        );
    }

    public function role(): ?string
    {
        $user = $this->user();

        return $user['role'] ?? null;
    }

    public function isAdmin(): bool
    {
        return $this->role() === 'admin';
    }

    /**
     * Hash a plaintext password using the strongest available algorithm.
     */
    public static function hash(string $password): string
    {
        $algo = defined('PASSWORD_ARGON2ID') ? PASSWORD_ARGON2ID : PASSWORD_DEFAULT;

        return password_hash($password, $algo);
    }
}
