<?php

declare(strict_types=1);

namespace App\Services;

use App\Core\Database;

/**
 * PasswordResetService
 *
 * Backs the Forgot Password / Reset Password pages. Deliberately never
 * reveals whether a given email exists in the system — sendResetLink()
 * always returns a generic success message regardless, so the flow
 * can't be used to enumerate registered accounts.
 */
final class PasswordResetService
{
    public function __construct(
        private readonly MailService $mailer = new MailService(),
        private readonly PasswordPolicyService $passwordPolicy = new PasswordPolicyService()
    ) {
    }

    public function sendResetLink(string $email, ?string $ipAddress = null): void
    {
        $pdo = Database::connection();

        $stmt = $pdo->prepare("SELECT id, first_name FROM users WHERE email = :email AND deleted_at IS NULL AND status = 'active'");
        $stmt->execute(['email' => $email]);
        $user = $stmt->fetch();

        // Always behave the same whether or not the account exists —
        // no early return, no different response shape, no timing
        // shortcut — see class docblock.
        if ($user === false) {
            return;
        }

        $token = bin2hex(random_bytes(32));

        $pdo->prepare(
            'INSERT INTO password_resets (user_id, token_hash, ip_address, expires_at, created_at)
             VALUES (:user_id, :hash, :ip, :expires, NOW())'
        )->execute([
            'user_id' => $user['id'],
            'hash' => hash('sha256', $token),
            'ip' => $ipAddress,
            'expires' => date('Y-m-d H:i:s', time() + 3600), // 1 hour
        ]);

        $resetUrl = base_url('reset-password/' . $token);

        $this->mailer->send(
            $email,
            $user['first_name'],
            'Reset your A&B Hub password',
            "<p>Hi {$user['first_name']},</p>"
            . '<p>We received a request to reset your password. This link expires in 1 hour:</p>'
            . "<p><a href=\"{$resetUrl}\">{$resetUrl}</a></p>"
            . "<p>If you didn't request this, you can safely ignore this email — your password will not change.</p>"
        );

        ActivityLogger::logAction('Password reset link requested', 'security', 'user', (int) $user['id']);
    }

    /** @return array{success:bool, message:string, errors?:array} */
    public function reset(string $rawToken, string $newPassword): array
    {
        $pdo = Database::connection();
        $hash = hash('sha256', $rawToken);

        $stmt = $pdo->prepare('SELECT * FROM password_resets WHERE token_hash = :hash AND used_at IS NULL LIMIT 1');
        $stmt->execute(['hash' => $hash]);
        $row = $stmt->fetch();

        if ($row === false) {
            return ['success' => false, 'message' => 'This reset link is invalid or has already been used.'];
        }

        if (strtotime($row['expires_at']) < time()) {
            return ['success' => false, 'message' => 'This reset link has expired. Please request a new one.'];
        }

        $userId = (int) $row['user_id'];

        $complexityErrors = $this->passwordPolicy->validateComplexity($newPassword);
        if ($complexityErrors !== []) {
            return ['success' => false, 'message' => 'Password does not meet requirements.', 'errors' => ['password' => $complexityErrors]];
        }

        if ($this->passwordPolicy->isReused($userId, $newPassword)) {
            return ['success' => false, 'message' => 'You cannot reuse a recent password. Please choose a new one.'];
        }

        $this->passwordPolicy->commit($userId, $newPassword);

        $pdo->prepare('UPDATE password_resets SET used_at = NOW() WHERE id = :id')->execute(['id' => $row['id']]);

        // Invalidate every other outstanding reset token for this user.
        $pdo->prepare('UPDATE password_resets SET used_at = NOW() WHERE user_id = :id AND used_at IS NULL')
            ->execute(['id' => $userId]);

        // A password reset is exactly the moment to also kill every
        // active session — if the account was compromised, this cuts
        // the attacker off immediately.
        (new SessionService())->revokeAll($userId);

        ActivityLogger::logAction('Password reset completed', 'security', 'user', $userId);

        return ['success' => true, 'message' => 'Your password has been reset. Please sign in.'];
    }
}
