<?php

declare(strict_types=1);

namespace App\Administration\Infrastructure\Security;

use App\Administration\Domain\Exception\EmailInvalideException;
use App\Administration\Domain\Model\User\Email;
use App\Administration\Domain\Model\User\StatutCompte;
use App\Administration\Domain\Repository\UserRepository;
use App\Shared\Infrastructure\Tenant\TenantId;
use App\Shared\Infrastructure\Tenant\TenantNotFoundException;
use App\Shared\Infrastructure\Tenant\TenantResolver;
use InvalidArgumentException;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException;
use Symfony\Component\Security\Core\Exception\UserNotFoundException as SymfonyUserNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;

/**
 * Loads users from the domain for Symfony authentication.
 *
 * This provider bridges Symfony Security with our Domain Layer.
 * It never reveals whether a user exists or not for security reasons.
 * Users are isolated by tenant (school).
 *
 * @implements UserProviderInterface<SecurityUser>
 *
 * @see Story 1.4 - User login (AC2: no account existence disclosure)
 */
final readonly class DatabaseUserProvider implements UserProviderInterface
{
    public function __construct(
        private UserRepository $userRepository,
        private TenantResolver $tenantResolver,
        private RequestStack $requestStack,
        private SecurityUserFactory $securityUserFactory,
    ) {
    }

    public function loadUserByIdentifier(string $identifier): UserInterface
    {
        $tenantId = $this->getCurrentTenantId();

        try {
            $email = new Email($identifier);
        } catch (EmailInvalideException) {
            // Malformed email = treat as user not found (security: generic error)
            throw new SymfonyUserNotFoundException();
        }

        $user = $this->userRepository->findByEmail($email, $tenantId);

        // Generic message to not reveal account existence
        if ($user === null) {
            throw new SymfonyUserNotFoundException();
        }

        // Blocked account: specific message so the user understands why they can't log in
        if ($user->statut === StatutCompte::SUSPENDU) {
            throw new CustomUserMessageAccountStatusException(
                'Votre compte a été suspendu. Contactez votre établissement.',
                messageData: ['statut' => 'suspended'],
            );
        }

        // Other non-active statuses (pending, consent, archived): generic error
        if (!$user->peutSeConnecter()) {
            throw new SymfonyUserNotFoundException();
        }

        return $this->securityUserFactory->fromDomainUser($user);
    }

    public function refreshUser(UserInterface $user): UserInterface
    {
        if (!$user instanceof SecurityUser) {
            throw new InvalidArgumentException('Expected instance of ' . SecurityUser::class);
        }

        return $this->loadUserByIdentifier($user->email());
    }

    public function supportsClass(string $class): bool
    {
        return $class === SecurityUser::class;
    }

    /**
     * Resolves the current tenant from the request host.
     *
     * @throws SymfonyUserNotFoundException if tenant cannot be resolved (security: generic error)
     */
    private function getCurrentTenantId(): TenantId
    {
        $request = $this->requestStack->getCurrentRequest();

        if ($request === null) {
            throw new SymfonyUserNotFoundException();
        }

        $host = $request->getHost();

        // Dev/test fallback: localhost uses ecole-alpha tenant
        if ($host === 'localhost' || $host === '127.0.0.1') {
            try {
                return $this->tenantResolver->resolve('ecole-alpha.classeo.local')->tenantId;
            } catch (TenantNotFoundException) {
                throw new SymfonyUserNotFoundException();
            }
        }

        try {
            $tenantConfig = $this->tenantResolver->resolve($host);

            return $tenantConfig->tenantId;
        } catch (TenantNotFoundException) {
            // Don't reveal tenant doesn't exist - use same error as invalid credentials
            throw new SymfonyUserNotFoundException();
        }
    }
}
