<?php

declare(strict_types=1);

namespace apexl\Io\modules\user\Actions;

use apexl\Config\ConfigurationInterface;
use apexl\Io\modules\user\entities\userEntity;
use apexl\Io\modules\user\enums\TwoFactorGateDecision;
use apexl\Io\modules\user\enums\TwoFactorMode;
use apexl\Io\modules\user\services\twoFactorAuth\TwoFactorAuthService;
use DateInterval;
use DateTimeImmutable;

/**
 * Resolves the platform 2FA mode + a user's enrolment state (and, in enforced
 * mode, the grace period) into a single decision (Plan 007 §5.3). Used both at
 * login (to shape the response) and per-request (by TwoFactorMiddleware). It
 * says whether a gate is required; the middleware combines that with the
 * session's passed-flag to decide whether to actually block.
 */
final readonly class TwoFactorGateAction
{
    public function __construct(
        private ConfigurationInterface $config,
        private TwoFactorAuthService $twoFactorService,
    ) {}

    public function evaluate(userEntity $user): TwoFactorGateDecision
    {
        return match ($this->twoFactorService->mode()) {
            TwoFactorMode::OFF => TwoFactorGateDecision::NONE,

            // Optional: only users who have actually enrolled are gated. Platform
            // config alone never forces anyone.
            TwoFactorMode::OPTIONAL => $this->twoFactorService->isEnabledForUser($user)
                ? TwoFactorGateDecision::CHALLENGE
                : TwoFactorGateDecision::NONE,

            // Enforced: an active method is mandatory. Enrolled users get the
            // challenge; unenrolled users must set up — unless still inside the
            // grace window, during which they're nagged (frontend) but not
            // blocked.
            TwoFactorMode::ENFORCED => $this->twoFactorService->hasActiveMethod($user)
                ? TwoFactorGateDecision::CHALLENGE
                : ($this->withinGracePeriod($user)
                    ? TwoFactorGateDecision::NONE
                    : TwoFactorGateDecision::SETUP_REQUIRED),
        };
    }

    /**
     * Enforced-mode grace: an unenrolled user is not blocked until
     * `grace_period_days` have elapsed since their account was created. 0 (the
     * default) = no grace, block immediately. Kept deliberately simple per
     * Plan 007 §9 — a fleet-wide rollout is an operational concern, not
     * something the framework solves cleverly on day one.
     */
    private function withinGracePeriod(userEntity $user): bool
    {
        $graceDays = (int) $this->config->get('auth.two_factor.grace_period_days');
        if ($graceDays <= 0) {
            return false;
        }

        $created = $user->created;
        if (!$created instanceof DateTimeImmutable) {
            return false;
        }

        $deadline = $created->add(new DateInterval(sprintf('P%dD', $graceDays)));

        return new DateTimeImmutable() < $deadline;
    }
}
