<?php

namespace App\Auth\Security;

use App\Auth\Repository\UserRepository;
use App\Shared\Captcha\CaptchaGuard;
use App\Shared\Constant\ErrorCodes;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
use Symfony\Component\RateLimiter\RateLimiterFactory;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Contracts\Cache\CacheInterface;

class PasswordAuthenticator extends AbstractAuthenticator
{
    public function __construct(
        private readonly UserRepository           $userRepository,
        private readonly JWTTokenManagerInterface $jwtManager,
        private readonly CacheInterface           $cache,
        private readonly LoggerInterface          $logger,
        private readonly RateLimiterFactory       $loginLimiter,
        private readonly CaptchaGuard             $captcha,
        private readonly int                      $refreshTokenTtl = 2592000,
        private readonly ?string                  $crawlerServiceToken = null,
    ) {}

    /**
     * لاگین سرویسی کرالر: هدر X-Service-Token با مقدار env CRAWLER_SERVICE_TOKEN
     * فقط کپچا را دور می‌زند — rate limit و اعتبارسنجی رمز دست‌نخورده می‌مانند.
     * env خالی = هیچ bypass (secure by default).
     */
    private function isTrustedServiceLogin(Request $request): bool
    {
        return ($this->crawlerServiceToken ?? '') !== ''
            && hash_equals($this->crawlerServiceToken, (string) $request->headers->get('X-Service-Token', ''));
    }

    public function supports(Request $request): ?bool
    {
        return $request->getPathInfo() === '/api/v1/user/login'
            && $request->isMethod('POST');
    }

    public function authenticate(Request $request): Passport
    {
        $limiter = $this->loginLimiter->create($request->getClientIp() ?? 'unknown');
        if (!$limiter->consume(1)->isAccepted()) {
            throw new TooManyRequestsHttpException(60, 'تعداد تلاش‌های ورود از حد مجاز گذشت');
        }

        // AppException را ExceptionSubscriber به پاسخ 422 با ERR_CAPTCHA_001 تبدیل می‌کند.
        if (!$this->isTrustedServiceLogin($request)) {
            $this->captcha->assertValid($request);
        }

        $data   = json_decode($request->getContent(), true) ?? [];
        $mobile = trim($data['mobile_number'] ?? '');
        $pass   = $data['password'] ?? '';

        return new Passport(
            new UserBadge($mobile, fn(string $id) => $this->userRepository->findByMobile($id)),
            new PasswordCredentials($pass)
        );
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        $user = $token->getUser();

        if (!$user->isStaff()) {
            return new JsonResponse([
                'success' => false,
                'data'    => null,
                'errors'  => [['code' => ErrorCodes::ERR_AUTH_006, 'message' => ErrorCodes::message(ErrorCodes::ERR_AUTH_006)]],
            ], 403);
        }

        $accessToken = $this->jwtManager->create($user);
        $rawToken = bin2hex(random_bytes(32));
        $cacheKey = 'refresh_' . hash('sha256', $rawToken);

        $item = $this->cache->getItem($cacheKey);
        $item->set((string) $user->getId());
        $item->expiresAfter($this->refreshTokenTtl);
        $this->cache->save($item);

        $this->logger->info('login_success', [
            'user_id' => $user->getId(),
            'ip'      => $request->getClientIp(),
            'method'  => 'password',
        ]);

        return new JsonResponse([
            'access_token'             => $accessToken,
            'refresh_token'            => $rawToken,
            'token_type'               => 'Bearer',
            'expires_in'               => 900,
            'refresh_token_expires_in' => $this->refreshTokenTtl,
        ]);
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
    {
        $this->logger->warning('login_failed', [
            'ip'     => $request->getClientIp(),
            'reason' => $exception->getMessage(),
        ]);

        return new JsonResponse([
            'success' => false,
            'data'    => null,
            'errors'  => [['code' => ErrorCodes::ERR_AUTH_005, 'message' => ErrorCodes::message(ErrorCodes::ERR_AUTH_005)]],
        ], 401);
    }
}
