<?php

namespace Modules\Auth\Services;

use App\Models\ActivityLogModel;
use Config\Auth as AuthConfig;
use Modules\Auth\Models\PasswordHistoryModel;
use Modules\Auth\Models\PasswordResetModel;
use Modules\Auth\Models\UserModel;

class PasswordPolicyService
{
    protected AuthConfig $config;
    protected UserModel $users;
    protected PasswordHistoryModel $history;
    protected PasswordResetModel $resets;
    protected ActivityLogModel $activity;

    public function __construct()
    {
        $this->config   = config('Auth');
        $this->users    = new UserModel();
        $this->history  = new PasswordHistoryModel();
        $this->resets   = new PasswordResetModel();
        $this->activity = new ActivityLogModel();
    }

    /**
     * Validate a plaintext password against the configured strength policy.
     * Returns an array of human-readable error strings (empty = valid).
     */
    public function validateStrength(string $password): array
    {
        $errors = [];

        if (mb_strlen($password) < $this->config->passwordMinLength) {
            $errors[] = "Password must be at least {$this->config->passwordMinLength} characters long.";
        }
        if ($this->config->passwordRequireMixedCase && (! preg_match('/[a-z]/', $password) || ! preg_match('/[A-Z]/', $password))) {
            $errors[] = 'Password must contain both uppercase and lowercase letters.';
        }
        if ($this->config->passwordRequireNumber && ! preg_match('/[0-9]/', $password)) {
            $errors[] = 'Password must contain at least one number.';
        }
        if ($this->config->passwordRequireSpecialChar && ! preg_match('/[^a-zA-Z0-9]/', $password)) {
            $errors[] = 'Password must contain at least one special character.';
        }

        return $errors;
    }

    public function isReused(int $userId, string $password): bool
    {
        if ($this->config->passwordHistoryLimit <= 0) {
            return false;
        }

        foreach ($this->history->recentHashes($userId, $this->config->passwordHistoryLimit) as $hash) {
            if (password_verify($password, $hash)) {
                return true;
            }
        }

        return false;
    }

    public function setPassword(int $userId, string $newPlainPassword): void
    {
        $hash = password_hash($newPlainPassword, PASSWORD_BCRYPT, ['cost' => 12]);

        $this->users->update($userId, [
            'password_hash'        => $hash,
            'password_changed_at'  => date('Y-m-d H:i:s'),
            'must_change_password' => 0,
        ]);

        $this->history->record($userId, $hash);
        $this->history->pruneOlderThan($userId, $this->config->passwordHistoryLimit ?: 5);
        $this->activity->log('auth', 'password_changed', $userId, 'Password changed');
    }

    /**
     * Start a forgot-password flow. Always returns a token when the user
     * exists; the controller decides whether to reveal that to the caller
     * (it should not, to avoid user enumeration — always show a generic
     * "if that account exists, an email was sent" message).
     */
    public function createResetToken(string $identifier, string $ip): ?string
    {
        $user = $this->users->findByLogin($identifier);
        if (! $user) {
            return null;
        }

        $plainToken = bin2hex(random_bytes(32));
        $this->resets->createToken($user->id, $plainToken, $ip, $this->config->passwordResetTokenTtlMinutes);
        $this->activity->log('auth', 'password_reset_requested', $user->id, 'Password reset requested');

        return $plainToken;
    }

    public function consumeResetToken(string $plainToken, string $newPassword): bool
    {
        $row = $this->resets->findValidToken($plainToken);
        if (! $row) {
            return false;
        }

        $this->setPassword((int) $row['user_id'], $newPassword);
        $this->resets->markUsed((int) $row['id']);

        return true;
    }

    public function changePassword(int $userId, string $currentPassword, string $newPassword): array
    {
        $user = $this->users->find($userId);
        if (! $user || ! password_verify($currentPassword, $user->password_hash)) {
            return ['success' => false, 'error' => 'Current password is incorrect.'];
        }

        $strengthErrors = $this->validateStrength($newPassword);
        if ($strengthErrors) {
            return ['success' => false, 'error' => implode(' ', $strengthErrors)];
        }

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

        $this->setPassword($userId, $newPassword);

        return ['success' => true];
    }
}
