<?php

/*
 * This file is part of the VITICORE2 package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CpCreation\VitiCore\Bridge\Symfony\Bundle\Controller\Admin;

use CpCreation\VitiCore\Bridge\Symfony\Bundle\Form\Admin\ClientCreateType;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Form\Admin\ClientPartnerGroupType;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Form\Admin\Model\ClientCreateData;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Security\AccountActivationService;
use CpCreation\VitiCore\Cart\CustomerOrderInsightsBuilder;
use CpCreation\VitiCore\Cart\Repository\CartRepository;
use CpCreation\VitiCore\Mailer\Repository\EmailLogRepository;
use CpCreation\VitiCore\Product\Repository\ProductPriceGroupRepository;
use CpCreation\VitiCore\User\Model\User;
use CpCreation\VitiCore\User\Repository\UserRepository;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[Route('/clients')]
#[IsGranted(new Expression("is_granted('ROLE_ADMIN')"))]
final class ClientController extends AbstractController
{
    public function __construct(
        private readonly UserRepository $userRepository,
        private readonly CartRepository $cartRepository,
        private readonly EmailLogRepository $emailLogRepository,
        private readonly ProductPriceGroupRepository $productPriceGroupRepository,
        private readonly AccountActivationService $accountActivationService,
        private readonly CustomerOrderInsightsBuilder $customerOrderInsightsBuilder,
    ) {
    }

    #[Route('', name: 'admin_client')]
    public function listAllClients(Request $request): Response
    {
        $filterOptions = $this->buildFilterOptions();
        $filters = $this->buildFilters($request, $filterOptions);
        $items = $this->userRepository->findRegisteredClientsFiltered(
            $filters['group_id'],
            $filters['marketing_opt_in'],
            $filters['has_orders'],
            $filters['is_verified'],
            $filters['search'],
        );

        return $this->render('@CpCreationVitiCore/admin/client/index.html.twig', [
            'items' => $items,
            'filters' => $filters,
            'filter_options' => $filterOptions,
            'users_with_orders' => array_fill_keys($this->cartRepository->findUserIdsWithOrders($items), true),
        ]);
    }

    #[Route('/non-enregistres', name: 'admin_guest_client')]
    public function listGuestClients(): Response
    {
        return $this->render('@CpCreationVitiCore/admin/client/guest.html.twig', [
            'items' => $this->cartRepository->findUnregisteredCustomerOrderSummaries(),
        ]);
    }

    #[Route('/non-enregistres/{id}', name: 'admin_guest_client_show', requirements: ['id' => '[0-9a-fA-F-]{36}'])]
    public function showGuestClient(string $id): Response
    {
        $history = $this->cartRepository->findUnregisteredCustomerOrderHistory($id);
        if (null === $history) {
            throw $this->createNotFoundException();
        }

        return $this->render('@CpCreationVitiCore/admin/client/guest_show.html.twig', [
            'customer' => $history['customer'],
            'order_insights' => $this->customerOrderInsightsBuilder->build($history['orders']),
        ]);
    }

    #[Route('/export/excel', name: 'admin_client_export_excel')]
    public function exportExcel(Request $request): Response
    {
        $filterOptions = $this->buildFilterOptions();
        $filters = $this->buildFilters($request, $filterOptions);
        $items = $this->userRepository->findRegisteredClientsFiltered(
            $filters['group_id'],
            $filters['marketing_opt_in'],
            $filters['has_orders'],
            $filters['is_verified'],
            $filters['search'],
        );

        if ([] === $items) {
            $this->addFlash('alert-warning', 'Aucun client ne correspond aux filtres sélectionnés.');

            return $this->redirectToRoute('admin_client', $filters['query_parameters']);
        }

        $usersWithOrders = array_fill_keys($this->cartRepository->findUserIdsWithOrders($items), true);

        $spreadsheet = new Spreadsheet();
        $sheet = $spreadsheet->getActiveSheet();
        $sheet->setTitle('Clients');
        $sheet->fromArray([
            'Identifiant',
            'Email',
            'Compte vérifié',
            'Groupe partenaire',
            'Option marketing',
            'Commande deja realisee',
        ], null, 'A1');

        $row = 2;
        foreach ($items as $item) {
            $sheet->fromArray([
                $item->getUsername(),
                $item->getEmail() ?? '',
                $item->isVerified() ? 'Oui' : 'Non',
                $item->getGroup()?->getName() ?? '',
                $item->isMarketingOptIn() ? 'Oui' : 'Non',
                isset($usersWithOrders[$item->getId()->toString()]) ? 'Oui' : 'Non',
            ], null, 'A'.$row);

            ++$row;
        }

        foreach (range('A', 'F') as $column) {
            $sheet->getColumnDimension($column)->setAutoSize(true);
        }

        $filePath = $this->createTempFilePath('clients_', 'xlsx');
        $writer = new Xlsx($spreadsheet);
        $writer->save($filePath);

        return $this->createDownloadResponse(
            $filePath,
            'clients_'.$this->buildExportSuffix($filters).'.xlsx',
            'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
        );
    }

    #[Route('/new', name: 'admin_client_new')]
    public function new(Request $request): Response
    {
        $data = new ClientCreateData();
        $data->group = $this->productPriceGroupRepository->getGroupByDefault()
            ?? $this->productPriceGroupRepository->findOneBy(['name' => 'Public']);

        $form = $this->createForm(ClientCreateType::class, $data);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $normalizedEmail = $this->normalizeEmail((string) $data->email);
            $existingUser = $this->userRepository->findOneBy(['email' => $normalizedEmail]);

            if (null !== $existingUser) {
                $form->get('email')->addError(new FormError('Cet email est déjà enregistré en base.'));
            }

            if (0 === $form->getErrors(true)->count()) {
                $data->email = $normalizedEmail;

                $user = new User();
                $data->applyToUser($user);
                $this->userRepository->save($user);

                $this->addFlash('alert-success', 'Le client '.$user->getUsername().' a été ajouté avec succès.');

                return $this->redirectToRoute('admin_client_show', [
                    'id' => $user->getId(),
                ]);
            }
        }

        return $this->render('@CpCreationVitiCore/admin/client/new.html.twig', [
            'form' => $form->createView(),
        ]);
    }

    #[Route('/{id}', name: 'admin_client_show', requirements: ['id' => '[0-9a-fA-F-]{36}'])]
    public function show(User $user, Request $request): Response
    {
        $completedOrders = $this->cartRepository->findCompletedAdminOrdersByUser($user);
        $hasOrders = [] !== $completedOrders;
        $form = $this->createForm(ClientPartnerGroupType::class, $user);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $user->setUpdatedAt(new \DateTimeImmutable());
            $this->userRepository->save($user);

            $this->addFlash('alert-success', 'Le groupe partenaire du client a été mis à jour.');

            return $this->redirectToRoute('admin_client_show', [
                'id' => $user->getId(),
            ]);
        }

        return $this->render('@CpCreationVitiCore/admin/client/show.html.twig', [
            'item' => $user,
            'can_delete' => !$hasOrders && !$user->isVerified(),
            'group_form' => $form->createView(),
            'recent_orders' => \array_slice($completedOrders, 0, 10),
            'order_insights' => $this->customerOrderInsightsBuilder->build($completedOrders),
            'email_logs' => $this->emailLogRepository->findRecentByUser($user, 30),
        ]);
    }

    #[Route('/{id}/delete', name: 'admin_client_delete', requirements: ['id' => '[0-9a-fA-F-]{36}'], methods: ['POST'])]
    public function delete(User $user, Request $request): RedirectResponse
    {
        if (!$this->isCsrfTokenValid('delete-client-'.$user->getId()->toString(), (string) $request->request->get('_token'))) {
            $this->addFlash('alert-danger', 'Le formulaire de suppression du client est invalide.');

            return $this->redirectToRoute('admin_client_show', [
                'id' => $user->getId(),
            ]);
        }

        if ([] !== $user->getAssignableRoles()) {
            $this->addFlash('alert-danger', 'Ce compte ne peut pas être supprimé depuis la gestion des clients.');

            return $this->redirectToRoute('admin_client_show', [
                'id' => $user->getId(),
            ]);
        }

        if ($user->isVerified()) {
            $this->addFlash('alert-danger', 'Ce client ne peut pas être supprimé car son compte a déjà été vérifié.');

            return $this->redirectToRoute('admin_client_show', [
                'id' => $user->getId(),
            ]);
        }

        if ($this->userHasOrders($user)) {
            $this->addFlash('alert-danger', 'Ce client ne peut pas être supprimé car il possède au moins une commande.');

            return $this->redirectToRoute('admin_client_show', [
                'id' => $user->getId(),
            ]);
        }

        $username = $user->getUsername();
        $this->userRepository->delete($user);
        $this->addFlash('alert-success', 'Le client '.$username.' a été supprimé avec succès.');

        return $this->redirectToRoute('admin_client');
    }

    #[Route('/{id}/verify', name: 'admin_client_verify', requirements: ['id' => '[0-9a-fA-F-]{36}'], methods: ['POST'])]
    public function verify(User $user, Request $request): RedirectResponse
    {
        if (!$this->isCsrfTokenValid('verify-client-'.$user->getId()->toString(), (string) $request->request->get('_token'))) {
            $this->addFlash('alert-danger', 'Le formulaire de vérification du client est invalide.');

            return $this->redirectToRoute('admin_client_show', [
                'id' => $user->getId(),
            ]);
        }

        if ($user->isVerified()) {
            $this->addFlash('alert-warning', 'Ce client est déjà vérifié.');

            return $this->redirectToRoute('admin_client_show', [
                'id' => $user->getId(),
            ]);
        }

        $this->accountActivationService->verify($user);
        $this->addFlash('alert-success', 'Le client '.$user->getUsername().' a été vérifié manuellement.');

        return $this->redirectToRoute('admin_client_show', [
            'id' => $user->getId(),
        ]);
    }

    private function normalizeEmail(string $email): string
    {
        return mb_strtolower(mb_trim($email));
    }

    private function userHasOrders(User $user): bool
    {
        return [] !== $this->cartRepository->findUserIdsWithOrders([$user]);
    }

    /**
     * @return array{
     *     groups: array<string, string>,
     *     marketing_choices: array<int, string>,
     *     order_choices: array<int, string>,
     *     verification_choices: array<int, string>
     * }
     */
    private function buildFilterOptions(): array
    {
        $groups = ['__none__' => 'Sans groupe'];
        $availableGroups = $this->productPriceGroupRepository->findNotSoftDeletable();
        usort($availableGroups, static function ($left, $right): int {
            $leftPosition = $left->getPosition() ?? \PHP_INT_MAX;
            $rightPosition = $right->getPosition() ?? \PHP_INT_MAX;

            if ($leftPosition !== $rightPosition) {
                return $leftPosition <=> $rightPosition;
            }

            return ($left->getName() ?? '') <=> ($right->getName() ?? '');
        });

        foreach ($availableGroups as $group) {
            $groupName = $group->getName();
            if (null === $groupName || '' === $groupName) {
                continue;
            }

            $groups[$group->getId()->toString()] = $groupName;
        }

        return [
            'groups' => $groups,
            'marketing_choices' => [
                '1' => 'Oui',
                '0' => 'Non',
            ],
            'order_choices' => [
                '1' => 'Oui',
                '0' => 'Non',
            ],
            'verification_choices' => [
                '1' => 'Oui',
                '0' => 'Non',
            ],
        ];
    }

    /**
     * @param array{
     *     groups: array<string, string>,
     *     marketing_choices: array<int, string>,
     *     order_choices: array<int, string>,
     *     verification_choices: array<int, string>
     * } $filterOptions
     *
     * @return array{
     *     group_id: ?string,
     *     search: ?string,
     *     marketing_opt_in: ?bool,
     *     marketing_opt_in_input: ?string,
     *     has_orders: ?bool,
     *     has_orders_input: ?string,
     *     is_verified: ?bool,
     *     is_verified_input: ?string,
     *     query_parameters: array<string, string>
     * }
     */
    private function buildFilters(Request $request, array $filterOptions): array
    {
        $groupId = self::trimToNull($request->query->get('group_id'));
        $search = self::trimToNull($request->query->get('search'));
        $marketingOptInInput = self::trimToNull($request->query->get('marketing_opt_in'));
        $hasOrdersInput = self::trimToNull($request->query->get('has_orders'));
        $isVerifiedInput = self::trimToNull($request->query->get('is_verified'));

        if (null !== $groupId && !\array_key_exists($groupId, $filterOptions['groups'])) {
            $groupId = null;
        }

        if (null !== $marketingOptInInput && !\array_key_exists($marketingOptInInput, $filterOptions['marketing_choices'])) {
            $marketingOptInInput = null;
        }

        if (null !== $hasOrdersInput && !\array_key_exists($hasOrdersInput, $filterOptions['order_choices'])) {
            $hasOrdersInput = null;
        }

        if (null !== $isVerifiedInput && !\array_key_exists($isVerifiedInput, $filterOptions['verification_choices'])) {
            $isVerifiedInput = null;
        }

        return [
            'group_id' => $groupId,
            'search' => $search,
            'marketing_opt_in' => null === $marketingOptInInput ? null : '1' === $marketingOptInInput,
            'marketing_opt_in_input' => $marketingOptInInput,
            'has_orders' => null === $hasOrdersInput ? null : '1' === $hasOrdersInput,
            'has_orders_input' => $hasOrdersInput,
            'is_verified' => null === $isVerifiedInput ? null : '1' === $isVerifiedInput,
            'is_verified_input' => $isVerifiedInput,
            'query_parameters' => array_filter([
                'group_id' => $groupId,
                'search' => $search,
                'marketing_opt_in' => $marketingOptInInput,
                'has_orders' => $hasOrdersInput,
                'is_verified' => $isVerifiedInput,
            ], static fn (?string $value): bool => null !== $value && '' !== $value),
        ];
    }

    /**
     * @param array{
     *     group_id: ?string,
     *     search: ?string,
     *     marketing_opt_in: ?bool,
     *     marketing_opt_in_input: ?string,
     *     has_orders: ?bool,
     *     has_orders_input: ?string,
     *     is_verified: ?bool,
     *     is_verified_input: ?string,
     *     query_parameters: array<string, string>
     * } $filters
     */
    private function buildExportSuffix(array $filters): string
    {
        $parts = ['du_'.(new \DateTimeImmutable())->format('Y-m-d')];

        if (null !== $filters['group_id']) {
            $parts[] = '__none__' === $filters['group_id'] ? 'sans-groupe' : 'groupe';
        }

        if (null !== $filters['search']) {
            $parts[] = 'recherche';
        }

        if (null !== $filters['marketing_opt_in']) {
            $parts[] = $filters['marketing_opt_in'] ? 'marketing-oui' : 'marketing-non';
        }

        if (null !== $filters['has_orders']) {
            $parts[] = $filters['has_orders'] ? 'avec-commandes' : 'sans-commandes';
        }

        if (null !== $filters['is_verified']) {
            $parts[] = $filters['is_verified'] ? 'verifies' : 'non-verifies';
        }

        return implode('_', $parts);
    }

    private function createTempFilePath(string $prefix, string $extension): string
    {
        $temporaryPath = tempnam(sys_get_temp_dir(), $prefix);
        if (false === $temporaryPath) {
            throw new \RuntimeException('Impossible de créer un fichier temporaire.');
        }

        @unlink($temporaryPath);

        return $temporaryPath.'.'.$extension;
    }

    private function createDownloadResponse(string $filePath, string $downloadName, string $contentType): BinaryFileResponse
    {
        $response = $this->file($filePath, $downloadName, ResponseHeaderBag::DISPOSITION_ATTACHMENT);
        $response->headers->set('Content-Type', $contentType);
        $response->deleteFileAfterSend(true);

        return $response;
    }

    private static function trimToNull(mixed $value): ?string
    {
        if (!\is_string($value)) {
            return null;
        }

        $trimmed = mb_trim($value);

        return '' === $trimmed ? null : $trimmed;
    }
}
