<?php

namespace MintyPHP\Support;

use MintyPHP\Http\Request;
use MintyPHP\Router;
use MintyPHP\Service\Access\AuthorizationDecision;
use MintyPHP\Service\Access\AuthorizationService;
use MintyPHP\Service\Auth\AuthService;
use MintyPHP\Service\Tenant\TenantService;

// Session-based web auth guard. Enforces login and RBAC for page actions.
// For API token authentication, see ApiAuth instead.
class Guard
{
    // Re-check tenant assignment at most once per minute to pick up permission changes without re-login.
    private const TENANT_REFRESH_INTERVAL = 60;
    /** @var (callable(): AuthService)|null */
    private static $authServiceResolver = null;
    /** @var (callable(): TenantService)|null */
    private static $tenantServiceResolver = null;
    /** @var (callable(): AuthorizationService)|null */
    private static $authorizationServiceResolver = null;

    public static function configure(
        callable $authServiceResolver,
        callable $tenantServiceResolver,
        callable $authorizationServiceResolver
    ): void {
        self::$authServiceResolver = $authServiceResolver;
        self::$tenantServiceResolver = $tenantServiceResolver;
        self::$authorizationServiceResolver = $authorizationServiceResolver;
    }

    public static function requireLogin(string $redirect = 'login'): void
    {
        if (!isset($_SESSION['user'])) {
            Flash::error('Login required', 'login', 'login_required');
            Router::redirect($redirect);
        }
        self::refreshTenantContext();
        if (!empty($_SESSION['no_active_tenant'])) {
            self::authService()->logout();
            Flash::error('No active tenant assigned', 'login', 'no_active_tenant');
            Router::redirect($redirect);
        }
    }

    private static function validateCurrentTenant(): void
    {
        $currentTenant = $_SESSION['current_tenant'] ?? null;
        if (!$currentTenant || empty($currentTenant['id'])) {
            return;
        }

        $tenantId = (int) $currentTenant['id'];
        $tenant = self::tenantService()->findById($tenantId);
        if ($tenant) {
            return;
        }

        // Current tenant was deleted — reload session so the user is switched to another active tenant or logged out.
        $userId = (int) ($_SESSION['user']['id'] ?? 0);
        if ($userId > 0) {
            self::authService()->loadTenantDataIntoSession($userId);
        } else {
            unset($_SESSION['current_tenant']);
        }
    }

    private static function refreshTenantContext(): void
    {
        $userId = (int) ($_SESSION['user']['id'] ?? 0);
        if ($userId <= 0) {
            return;
        }
        $last = (int) ($_SESSION['tenant_context_refreshed_at'] ?? 0);
        $now = time();
        if ($last > 0 && ($now - $last) < self::TENANT_REFRESH_INTERVAL) {
            return;
        }
        self::authService()->loadTenantDataIntoSession($userId);
        $_SESSION['tenant_context_refreshed_at'] = $now;
    }

    public static function requireAbility(string $ability, string $redirect = 'admin', array $context = []): void
    {
        self::requireLogin();
        self::validateCurrentTenant();
        $userId = (int) ($_SESSION['user']['id'] ?? 0);
        if (!self::isAllowedByAbility($userId, $ability, $context)) {
            Flash::error('Permission denied', $redirect, 'permission_denied');
            Router::redirect($redirect);
        }
    }

    public static function requireAbilityOrForbidden(string $ability, array $context = []): void
    {
        self::requireAbilityDecisionOrForbidden($ability, $context);
    }

    public static function requireAbilityDecisionOrForbidden(string $ability, array $context = []): AuthorizationDecision
    {
        self::requireLogin();
        self::validateCurrentTenant();
        $userId = (int) ($_SESSION['user']['id'] ?? 0);
        $decision = self::authorizationService()->authorize($ability, [
            'actor_user_id' => $userId,
            ...$context,
        ]);
        if (!$decision->isAllowed()) {
            // JSON requests get a 403/404 response body; browser requests get a redirect to the forbidden page.
            if (Request::wantsJson()) {
                http_response_code($decision->status());
                header('Content-Type: application/json; charset=utf-8');
                $error = trim($decision->error());
                echo json_encode(['error' => $error !== '' ? $error : 'forbidden']);
                exit;
            }
            Router::redirect('error/forbidden?url=' . urlencode(Request::pathWithQuery()));
        }

        return $decision;
    }

    public static function deny(string $redirect = 'error/forbidden'): void
    {
        if (Request::wantsJson()) {
            http_response_code(403);
            header('Content-Type: application/json; charset=utf-8');
            echo json_encode(['error' => 'forbidden']);
            exit;
        }

        if ($redirect === 'error/forbidden') {
            Router::redirect('error/forbidden?url=' . urlencode(Request::pathWithQuery()));
            return;
        }

        Router::redirect($redirect);
    }

    private static function authorizationService(): AuthorizationService
    {
        if (!is_callable(self::$authorizationServiceResolver)) {
            throw new \RuntimeException('Guard is not configured for dependency: ' . AuthorizationService::class);
        }
        return (self::$authorizationServiceResolver)();
    }

    private static function authService(): AuthService
    {
        if (!is_callable(self::$authServiceResolver)) {
            throw new \RuntimeException('Guard is not configured for dependency: ' . AuthService::class);
        }
        return (self::$authServiceResolver)();
    }

    private static function tenantService(): TenantService
    {
        if (!is_callable(self::$tenantServiceResolver)) {
            throw new \RuntimeException('Guard is not configured for dependency: ' . TenantService::class);
        }
        return (self::$tenantServiceResolver)();
    }

    private static function isAllowedByAbility(int $userId, string $ability, array $context = []): bool
    {
        $ability = trim($ability);
        if ($userId <= 0 || $ability === '') {
            return false;
        }

        $decision = self::authorizationService()->authorize($ability, [
            'actor_user_id' => $userId,
            ...$context,
        ]);

        return $decision->isAllowed();
    }
}
