<?php declare(strict_types=1);
/*
 * This file is part of the SplendidBear Websites' projects.
 *
 * Copyright (c) 2026 @ www.splendidbear.org
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace App\Controller;

use App\Dto\BattleShareDto;
use App\Dto\ProfileChartDataFactory;
use App\Dto\ProfileGameDto;
use App\Dto\ProfileGameDtoFactory;
use App\Dto\ProfileStatsDto;
use App\Dto\ProfileViewDto;
use App\Entity\User;
use App\Entity\RecentBattle;
use App\Repository\PlayedGameRepository;
use App\Repository\RecentBattleRepository;
use App\Repository\UserStatsRepository;
use App\Service\BattleCardGenerator;
use App\Service\WebAuthnService;
use Doctrine\ORM\EntityManagerInterface;
use League\Flysystem\FilesystemException;
use League\Flysystem\FilesystemOperator;
use Liip\ImagineBundle\Imagine\Cache\CacheManager;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Uid\Uuid;
use Throwable;
use function count;

/**
 * Class ProfileController
 *
 * @package  App\Controller
 * @author   Lang <https://www.splendidbear.org>
 * @category Class
 * @license  https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
 * @link     www.splendidbear.org
 * @since    2026. 04. 11.
 */
#[AsController]
class ProfileController extends AbstractController
{
    public function __construct(
        private readonly LoggerInterface         $logger,
        private readonly PlayedGameRepository    $repo,
        private readonly UserStatsRepository     $userStatsRepo,
        private readonly RecentBattleRepository  $recentBattleRepo,
        private readonly WebAuthnService         $webAuthnService,
        private readonly ProfileGameDtoFactory   $profileGameDtoFactory,
        private readonly ProfileChartDataFactory $profileChartDataFactory,
    ) {
    }

    #[Route('/profile', name: 'MineSeekerBundle_profile')]
    public function index(): Response
    {
        /** @var User $user */
        $user = $this->getUser();
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');

        $userId = $user->id;
        $stats = ProfileStatsDto::fromUserStats($this->userStatsRepo->findByUserId($userId));
        $recent = $this->recentBattleRepo->findRecentForUser($userId, 30);

        $gamesData = array_map(
            fn(RecentBattle $battle): ProfileGameDto => $this->profileGameDtoFactory->createFromRecentBattle($battle),
            $recent,
        );

        $chartData = $this->profileChartDataFactory->buildChartData($user, $userId, $stats);

        $view = new ProfileViewDto(
            stats: $stats,
            recent: $recent,
            gamesData: $gamesData,
            chartData: $chartData,
        );

        return $this->render('Security/profile.html.twig', $view->toTemplateContext());
    }

    #[Route(
        '/battle/{uuid}',
        name: 'MineSeekerBundle_battle_share',
        requirements: ['uuid' => '[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}'],
        methods: ['GET'],
    )]
    public function battleShare(Uuid $uuid): Response
    {
        $game = $this->repo->findOneByUuid($uuid);

        if (!$game) {
            throw $this->createNotFoundException('Battle not found.');
        }

        return $this->render('Game/battle_share.html.twig', BattleShareDto::fromPlayedGame($game)->toTemplateContext());
    }

    #[Route(
        '/og/battle/{uuid}.png',
        name: 'MineSeekerBundle_og_battle',
        requirements: ['uuid' => '[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}'],
        methods: ['GET'],
    )]
    public function battleOgImage(Uuid $uuid, BattleCardGenerator $generator): BinaryFileResponse
    {
        $game = $this->repo->findOneBy(['uuid' => $uuid]);
        if (!$game) {
            throw $this->createNotFoundException();
        }

        $path = $generator->generate($game);
        $response = new BinaryFileResponse($path);
        $response->headers->set('Content-Type', 'image/png');
        $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_INLINE);
        $response->setMaxAge(86400 * 30);
        $response->setPublic();

        return $response;
    }

    #[Route('/profile/avatar', name: 'MineSeekerBundle_profile_avatar', methods: ['POST'])]
    public function uploadAvatar(
        Request                                                             $request,
        EntityManagerInterface                                              $em,
        CacheManager                                                        $cacheManager,
        #[Autowire(service: 'mineseeker.media.storage')] FilesystemOperator $mediaStorage,
    ): JsonResponse {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');

        /** @var User $user */
        $user = $this->getUser();

        $file = $request->files->get('avatar');

        if (!$file instanceof UploadedFile) {
            return $this->json(['error' => 'No file uploaded.'], 400);
        }

        if ($file->getSize() > 2 * 1024 * 1024) {
            return $this->json(['error' => 'File is too large. Maximum 2 MB.'], 400);
        }

        $allowed = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
        if (!in_array($file->getMimeType(), $allowed, true)) {
            return $this->json(['error' => 'Invalid type. Allowed: JPEG, PNG, GIF, WEBP.'], 400);
        }

        $ext = $file->guessExtension() ?? 'jpg';
        $newPath = sprintf('avatar/%s.%s', Uuid::v4()->toRfc4122(), $ext);
        $oldPath = $user->avatarPath;

        /** Remove old file and any cached thumbnails */
        if ($oldPath) {
            try {
                $mediaStorage->delete($oldPath);
            } catch (Throwable) {
                $this->logger->error('Unable to delete old avatar: ' . $oldPath);
            }
            $cacheManager->remove($oldPath, 'avatar_thumb');
        }

        /** Upload original to MinIO media/avatar/ */
        $stream = fopen($file->getPathname(), 'rb');
        try {
            $mediaStorage->writeStream($newPath, $stream);
        } catch (FilesystemException $e) {
            $this->logger->error('Unable to write new avatar: ' . $e->getMessage());
            throw new RuntimeException('Unable to write new avatar: ' . $e->getMessage());
        }
        fclose($stream);

        $user->avatarPath = $newPath;
        $em->flush();

        return $this->json([
            'thumbUrl' => $cacheManager->generateUrl($newPath, 'avatar_thumb'),
        ]);
    }

    #[Route('/profile/security', name: 'MineSeekerBundle_profile_security')]
    public function security(): Response
    {
        /** @var User $user */
        $user = $this->getUser();
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');

        $credentials = $this->webAuthnService->getCredentialsForUser($user);
        $credentialsData = array_map(fn($cred) => [
            'id'                    => $cred->id,
            'credentialName'        => $cred->credentialName,
            'createdAt'             => $cred->createdAt?->format('Y-m-d H:i:s'),
            'lastUsedAt'            => $cred->lastUsedAt?->format('Y-m-d H:i:s'),
            'isBackupEligible'      => $cred->isBackupEligible,
            'isBackupAuthenticated' => $cred->isBackupAuthenticated,
        ], $credentials);

        return $this->render('Security/profile_security.html.twig', [
            'credentials'      => $credentialsData,
            'isTotpEnabled'    => $user->isTotpAuthenticationEnabled(),
            'backupCodesCount' => count($user->backupCodes),
        ]);
    }
}
