<?php

declare(strict_types=1);

namespace App\Controller;

use App\Entity\User;
use App\Repository\UserRepository;
use App\Security\RefreshTokenManager;
use Doctrine\ORM\EntityManagerInterface;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Attribute\Route;

#[Route('/api')]
class ApiAuthController extends AbstractController
{
    public function __construct(
        protected readonly UserRepository $userRepository,
        protected readonly EntityManagerInterface $entityManager,
        protected readonly UserPasswordHasherInterface $passwordHasher,
        protected readonly JWTTokenManagerInterface $jwtTokenManager,
        protected readonly RefreshTokenManager $refreshTokenManager,
    ) {
    }

    #[Route('/register', name: 'api_register', methods: ['POST'])]
    public function register(Request $request): JsonResponse
    {
        throw new NotFoundHttpException();

        try {
            $payload = $this->decodeJsonRequest($request);
        } catch (\InvalidArgumentException $exception) {
            return $this->json(['message' => $exception->getMessage()], Response::HTTP_BAD_REQUEST);
        }

        $email = mb_strtolower(trim((string) ($payload['email'] ?? '')));
        $plainPassword = (string) ($payload['password'] ?? '');

        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return $this->json(['message' => 'A valid email is required.'], Response::HTTP_BAD_REQUEST);
        }

        if (mb_strlen($plainPassword) < 8) {
            return $this->json(['message' => 'Password must be at least 8 characters long.'], Response::HTTP_BAD_REQUEST);
        }

        if ($this->userRepository->findOneByEmail($email) instanceof User) {
            return $this->json(['message' => 'A user with this email already exists.'], Response::HTTP_CONFLICT);
        }

        $user = new User($email);
        $user->setPassword($this->passwordHasher->hashPassword($user, $plainPassword));

        $this->entityManager->persist($user);
        $this->entityManager->flush();

        $accessToken = $this->jwtTokenManager->create($user);
        $refreshToken = $this->refreshTokenManager->createRefreshToken($user);

        return $this->json([
            'token' => $accessToken,
            'refresh_token' => $refreshToken,
            'user' => $this->buildUserResponse($user),
        ], Response::HTTP_CREATED);
    }

    #[Route('/token/refresh', name: 'api_token_refresh', methods: ['POST'])]
    public function refresh(Request $request): JsonResponse
    {
        try {
            $payload = $this->decodeJsonRequest($request);
        } catch (\InvalidArgumentException) {
            return $this->json(['message' => 'Invalid request.'], Response::HTTP_BAD_REQUEST);
        }

        $refreshToken = trim((string) ($payload['refresh_token'] ?? ''));

        if ($refreshToken === '') {
            return $this->json(['message' => 'Refresh token is required.'], Response::HTTP_BAD_REQUEST);
        }

        $rotation = $this->refreshTokenManager->rotateFromToken($refreshToken);

        if ($rotation === null) {
            return $this->json([
                'message' => 'Session expired. Please login again.',
            ], Response::HTTP_UNAUTHORIZED);
        }

        return $this->json([
            'token' => $rotation->accessToken,
            'refresh_token' => $rotation->refreshToken,
            'user' => $this->buildUserResponse($rotation->user),
        ]);
    }

    #[Route('/logout', name: 'api_logout', methods: ['POST'])]
    public function logout(Request $request): JsonResponse
    {
        try {
            $payload = $this->decodeJsonRequest($request);
        } catch (\InvalidArgumentException) {
            return $this->json(['message' => 'Logged out.']);
        }

        $refreshToken = trim((string) ($payload['refresh_token'] ?? ''));
        $this->refreshTokenManager->revokeToken($refreshToken);

        return $this->json(['message' => 'Logged out.']);
    }

    #[Route('/me', name: 'api_me', methods: ['GET'])]
    public function me(): JsonResponse
    {
        $user = $this->getAppUser();

        return $this->json([
            'user' => $this->buildUserResponse($user),
        ]);
    }

    /**
     * @return array<string, mixed>
     */
    private function decodeJsonRequest(Request $request): array
    {
        try {
            $payload = json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR);
        } catch (\JsonException) {
            throw new \InvalidArgumentException('Invalid JSON payload.');
        }

        if (!is_array($payload)) {
            throw new \InvalidArgumentException('Invalid JSON payload.');
        }

        return $payload;
    }

    /**
     * @return array<string, mixed>
     */
    private function buildUserResponse(User $user): array
    {
        return [
            'id' => $user->getId(),
            'email' => $user->getEmail(),
            'roles' => $user->getRoles(),
            'createdAt' => $user->getCreatedAt()->format(\DateTimeInterface::ATOM),
        ];
    }

    private function getAppUser(): User
    {
        $user = $this->getUser();
        if (!$user instanceof User) {
            throw new AccessDeniedHttpException();
        }

        return $user;
    }
}
