<?php

namespace MintyPHP\Http;

use MintyPHP\Repository\Access\RolePermissionRepository;
use MintyPHP\Repository\Access\UserRoleRepository;
use MintyPHP\Service\Access\AuthorizationService;
use MintyPHP\Service\Auth\ApiTokenService;
use MintyPHP\Service\Tenant\TenantScopeService;
use MintyPHP\Service\User\UserTenantContextService;

// Static facade for API authentication — state is set once per request during ApiBootstrap::init().
// Dependencies are injected as lazy resolvers so services are only instantiated when actually needed.
class ApiAuth
{
    /** @var (callable(): TenantScopeService)|null */
    private static $authScopeGatewayResolver = null;

    /** @var (callable(): ApiTokenService)|null */
    private static $apiTokenServiceResolver = null;

    /** @var (callable(): UserRoleRepository)|null */
    private static $userRoleRepositoryResolver = null;

    /** @var (callable(): RolePermissionRepository)|null */
    private static $rolePermissionRepositoryResolver = null;

    /** @var (callable(): UserTenantContextService)|null */
    private static $userTenantContextServiceResolver = null;
    /** @var (callable(): AuthorizationService)|null */
    private static $authorizationServiceResolver = null;

    private static ?array $currentUser = null;
    private static ?array $currentPermissions = null;
    private static ?int $currentTenantId = null;
    private static ?int $tokenTenantId = null;
    private static ?array $currentTokenRecord = null;
    private static bool $authenticated = false;

    public static function configure(
        callable $authScopeGatewayResolver,
        callable $apiTokenServiceResolver,
        callable $userRoleRepositoryResolver,
        callable $rolePermissionRepositoryResolver,
        callable $userTenantContextServiceResolver,
        callable $authorizationServiceResolver
    ): void {
        self::$authScopeGatewayResolver = $authScopeGatewayResolver;
        self::$apiTokenServiceResolver = $apiTokenServiceResolver;
        self::$userRoleRepositoryResolver = $userRoleRepositoryResolver;
        self::$rolePermissionRepositoryResolver = $rolePermissionRepositoryResolver;
        self::$userTenantContextServiceResolver = $userTenantContextServiceResolver;
        self::$authorizationServiceResolver = $authorizationServiceResolver;
    }

    /**
     * Extract the full Bearer token from the Authorization header.
     */
    public static function extractBearerToken(): string
    {
        $header = trim((string) ($_SERVER['HTTP_AUTHORIZATION'] ?? ''));
        if ($header === '') {
            // Nginx with PHP-FPM may expose the header under this key instead
            $header = trim((string) ($_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? ''));
        }
        if ($header === '' || stripos($header, 'Bearer ') !== 0) {
            return '';
        }

        return trim(substr($header, 7));
    }

    /**
     * Attempt to authenticate from Authorization: Bearer header.
     */
    public static function authenticate(): bool
    {
        self::$currentUser = null;
        self::$currentPermissions = null;
        self::$currentTenantId = null;
        self::$tokenTenantId = null;
        self::$currentTokenRecord = null;
        self::$authenticated = false;

        $bearerToken = self::extractBearerToken();
        if ($bearerToken === '') {
            return false;
        }

        $scopeGateway = self::scopeGateway();
        $result = self::apiTokenService()->validate($bearerToken);
        if ($result === null) {
            return false;
        }

        $user = $result['user'];
        $userId = (int) ($user['id'] ?? 0);
        $userRoleRepository = self::userRoleRepository();
        $rolePermissionRepository = self::rolePermissionRepository();
        $userTenantContextService = self::userTenantContextService();

        // Load permissions directly (bypass session cache)
        $roleIds = $userRoleRepository->listRoleIdsByUserId($userId);
        $permissions = $rolePermissionRepository->listPermissionKeysByRoleIds($roleIds);

        // Resolve tenant context: a token may be scoped to a specific tenant,
        // otherwise fall back to the user's active tenant context.
        $tokenTenantId = $result['tenant_id'];
        if ($tokenTenantId !== null) {
            // Verify the token's tenant is still assigned to this user
            $userTenantIds = $scopeGateway->getUserTenantIds($userId);
            if (!in_array($tokenTenantId, $userTenantIds, true)) {
                return false;
            }
            $currentTenantId = $tokenTenantId;
            self::$tokenTenantId = $tokenTenantId;
        } else {
            $currentTenantId = $userTenantContextService->getCurrentTenantId($userId);
            self::$tokenTenantId = null;
        }

        self::$currentUser = $user;
        self::$currentPermissions = $permissions;
        self::$currentTenantId = $currentTenantId;
        self::$currentTokenRecord = $result['token_record'];
        self::$authenticated = true;

        return true;
    }

    public static function isAuthenticated(): bool
    {
        return self::$authenticated;
    }

    public static function user(): ?array
    {
        return self::$currentUser;
    }

    public static function userId(): int
    {
        return (int) (self::$currentUser['id'] ?? 0);
    }

    public static function permissions(): array
    {
        return self::$currentPermissions ?? [];
    }

    public static function can(string $ability, array $context = []): bool
    {
        $decision = self::authorizationService()->authorize($ability, [
            'actor_user_id' => self::userId(),
            'scoped_tenant_id' => self::scopedTenantId(),
            ...$context,
        ]);
        return $decision->isAllowed();
    }

    // Effective tenant for data filtering — may come from token scope or user session.
    public static function tenantId(): ?int
    {
        return self::$currentTenantId;
    }

    // Non-null only when the token itself was issued for a specific tenant.
    // Used to restrict access in token-scoped requests (e.g. tenant integrations).
    public static function scopedTenantId(): ?int
    {
        return self::$tokenTenantId;
    }

    public static function tokenRecord(): ?array
    {
        return self::$currentTokenRecord;
    }

    public static function tokenId(): ?int
    {
        $id = (int) (self::$currentTokenRecord['id'] ?? 0);
        return $id > 0 ? $id : null;
    }

    public static function isTenantScopedToken(): bool
    {
        return (self::$tokenTenantId ?? 0) > 0;
    }

    /**
     * Require both tenant-scope and token-tenant access for a resource.
     */
    public static function requireResourceAccess(string $resource, int $resourceId): void
    {
        $scopeGateway = self::scopeGateway();
        if (!$scopeGateway->canAccess($resource, $resourceId, self::userId())) {
            ApiResponse::notFound();
        }
        self::requireTokenTenantAccess($resource, $resourceId);
    }

    public static function requireTokenTenantAccess(string $resource, int $resourceId): void
    {
        if (!self::isTenantScopedToken()) {
            return;
        }

        $tenantId = (int) (self::$tokenTenantId ?? 0);
        if ($tenantId <= 0) {
            ApiResponse::forbidden();
        }

        $scopeGateway = self::scopeGateway();
        if (!$scopeGateway->resourceBelongsToTenant($resource, $resourceId, $tenantId)) {
            // 404 instead of 403 — don't reveal that the resource exists in another tenant
            ApiResponse::notFound();
        }
    }

    /**
     * Check if the current user can self-manage API tokens.
     */
    public static function canSelfManageTokens(): bool
    {
        $decision = self::authorizationService()->authorize(
            \MintyPHP\Service\Access\OperationsAuthorizationPolicy::ABILITY_API_TOKENS_SELF_MANAGE,
            [
                'actor_user_id' => self::userId(),
                'scoped_tenant_id' => self::scopedTenantId(),
            ]
        );
        return $decision->isAllowed();
    }

    public static function requireSelfManageTokens(): void
    {
        if (!self::canSelfManageTokens()) {
            ApiResponse::forbidden('api_tokens_self_manage_forbidden');
        }
    }

    private static function scopeGateway(): TenantScopeService
    {
        return self::resolveDependency(self::$authScopeGatewayResolver, TenantScopeService::class, 'ApiAuth');
    }

    private static function apiTokenService(): ApiTokenService
    {
        return self::resolveDependency(self::$apiTokenServiceResolver, ApiTokenService::class, 'ApiAuth');
    }

    private static function userRoleRepository(): UserRoleRepository
    {
        return self::resolveDependency(self::$userRoleRepositoryResolver, UserRoleRepository::class, 'ApiAuth');
    }

    private static function rolePermissionRepository(): RolePermissionRepository
    {
        return self::resolveDependency(self::$rolePermissionRepositoryResolver, RolePermissionRepository::class, 'ApiAuth');
    }

    private static function userTenantContextService(): UserTenantContextService
    {
        return self::resolveDependency(self::$userTenantContextServiceResolver, UserTenantContextService::class, 'ApiAuth');
    }

    private static function authorizationService(): AuthorizationService
    {
        return self::resolveDependency(self::$authorizationServiceResolver, AuthorizationService::class, 'ApiAuth');
    }

    private static function resolveDependency(mixed $resolver, string $expectedClass, string $consumer): mixed
    {
        if (!is_callable($resolver)) {
            throw new \RuntimeException($consumer . ' is not configured for dependency: ' . $expectedClass);
        }

        $service = $resolver();
        if (!$service instanceof $expectedClass) {
            throw new \RuntimeException($consumer . ' resolver returned invalid dependency: ' . $expectedClass);
        }

        return $service;
    }

}
