<?php

namespace App\Controller;

use App\Entity\BilletBuyer;
use App\Entity\BilletOrder;
use App\Entity\Event;
use App\Entity\Payout;
use App\Entity\User;
use App\Service\AuditService;
use App\Service\BilletOrderService;
use App\Service\MailerService;
use App\Service\PayoutPdfService;
use App\Service\StripeService;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class StripeWebhookController extends AbstractController
{
    #[Route('/stripe/webhook', name: 'app_stripe_webhook', methods: ['POST'])]
    public function webhook(Request $request, StripeService $stripeService, EntityManagerInterface $em, MailerService $mailerService, PayoutPdfService $pdfService, BilletOrderService $billetOrderService, AuditService $audit, CacheItemPoolInterface $cache): Response
    {
        $payload = $request->getContent();
        $signature = $request->headers->get('Stripe-Signature', '');

        $event = $stripeService->verifyWebhookSignature($payload, $signature);

        if (!$event) {
            return new Response('Invalid signature', 400);
        }

        $eventId = $event->id ?? null;
        if ($eventId) {
            $cacheKey = 'stripe_wh_'.str_replace([':', '{', '}', '(', ')', '/', '\\', '@'], '_', $eventId);
            $item = $cache->getItem($cacheKey);
            if ($item->isHit()) {
                return new Response('OK', 200);
            }
            $item->set(true);
            $item->expiresAfter(86400);
            $cache->save($item);
        }

        $type = $event->type ?? null;

        match ($type) {
            'payout.created', 'payout.updated', 'payout.paid', 'payout.failed', 'payout.canceled' => $this->handlePayout($event, $em, $mailerService, $pdfService),
            'payment_intent.succeeded' => $this->handlePaymentIntentSucceeded($event, $em, $billetOrderService),
            'payment_intent.payment_failed' => $this->handlePaymentIntentFailed($event, $em, $mailerService, $audit),
            'charge.refunded' => $this->handleChargeRefunded($event, $em, $mailerService, $audit, $billetOrderService),
            'charge.dispute.created' => $this->handleDisputeCreated($event, $em, $mailerService),
            'checkout.session.completed' => $this->handleCheckoutSessionCompleted($event, $em, $mailerService),
            'invoice.payment_failed' => $this->handleInvoicePaymentFailed($event, $em, $mailerService),
            'customer.subscription.deleted' => $this->handleSubscriptionDeleted($event, $em, $mailerService),
            default => null,
        };

        return new Response('OK', 200);
    }

    #[Route('/stripe/webhook/connect', name: 'app_stripe_webhook_connect', methods: ['POST'])]
    public function webhookConnect(Request $request, StripeService $stripeService, EntityManagerInterface $em): Response
    {
        $payload = $request->getContent();
        $signature = $request->headers->get('Stripe-Signature', '');

        $event = $stripeService->verifyConnectWebhookSignature($payload, $signature);

        if (!$event) {
            return new Response('Invalid signature', 400);
        }

        $type = $event->type ?? null;
        $data = json_decode($payload, true) ?? [];

        match ($type) {
            'v2.core.account.created' => $this->handleAccountStatus($data, 'started', $em),
            'v2.core.account.updated' => $this->handleAccountStatus($data, 'updated', $em),
            'v2.core.account.closed' => $this->handleAccountStatus($data, 'closed', $em),
            'v2.core.account[configuration.merchant].capability_status_updated' => $this->handleCapabilityUpdate($data, $em),
            'v2.core.account[configuration.recipient].capability_status_updated' => $this->handleCapabilityUpdate($data, $em),
            default => null,
        };

        return new Response('OK', 200);
    }

    /**
     * @param array<string, mixed> $data
     */
    private function handleAccountStatus(array $data, string $status, EntityManagerInterface $em): void
    {
        $user = $this->findUserByEvent($data, $em);

        if (!$user) {
            return;
        }

        $user->setStripeStatus($status);

        if ('closed' === $status) {
            $user->setStripeChargesEnabled(false);
            $user->setStripePayoutsEnabled(false);
        }

        $em->flush();
    }

    /**
     * @param array<string, mixed> $data
     */
    private function handleCapabilityUpdate(array $data, EntityManagerInterface $em): void
    {
        $user = $this->findUserByEvent($data, $em);

        if (!$user) {
            return;
        }

        $after = $data['changes']['after']['configuration'] ?? [];

        $merchantCapabilities = $after['merchant']['capabilities'] ?? [];
        $cardPayments = $merchantCapabilities['card_payments']['status'] ?? null;
        if ('active' === $cardPayments) {
            $user->setStripeChargesEnabled(true);
        }

        $recipientCapabilities = $after['recipient']['capabilities'] ?? [];
        $payouts = $recipientCapabilities['stripe_balance']['payouts']['status'] ?? null;
        $merchantPayouts = $merchantCapabilities['stripe_balance']['payouts']['status'] ?? null;
        if ('active' === $payouts || 'active' === $merchantPayouts) {
            $user->setStripePayoutsEnabled(true);
        }

        if ($user->isStripeChargesEnabled() && $user->isStripePayoutsEnabled()) {
            $user->setStripeStatus('active');
        }

        $em->flush();
    }

    private function handlePaymentIntentSucceeded(\Stripe\Event $event, EntityManagerInterface $em, BilletOrderService $billetOrderService): void
    {
        $paymentIntent = $event->data->object;
        $orderId = $paymentIntent->metadata->order_id ?? null;

        if (!$orderId) {
            return;
        }

        $order = $em->getRepository(BilletBuyer::class)->find((int) $orderId);
        if (!$order || BilletBuyer::STATUS_PENDING !== $order->getStatus()) {
            return;
        }

        $debtOrganizerId = $paymentIntent->metadata->debt_organizer_id ?? null;
        if ($debtOrganizerId) {
            $organizer = $em->getRepository(User::class)->find((int) $debtOrganizerId);
            if ($organizer) {
                $organizer->reduceDebt($paymentIntent->amount ?? 0);
            }
        }

        $billetOrderService->generateOrderTickets($order);
        $billetOrderService->generateAndSendTickets($order);
        $billetOrderService->notifyOrganizer($order);
    }

    private function handlePaymentIntentFailed(\Stripe\Event $event, EntityManagerInterface $em, MailerService $mailerService, AuditService $audit): void
    {
        $paymentIntent = $event->data->object;
        $orderId = $paymentIntent->metadata->order_id ?? null;

        if (!$orderId) {
            return;
        }

        $order = $em->getRepository(BilletBuyer::class)->find((int) $orderId);
        if (!$order || BilletBuyer::STATUS_PENDING !== $order->getStatus()) {
            return;
        }

        $errorMessage = $paymentIntent->last_payment_error->message ?? 'Paiement refuse';

        $order->setStatus(BilletBuyer::STATUS_CANCELLED);
        $em->flush();

        $audit->log('payment_failed', 'BilletBuyer', $order->getId(), [
            'orderNumber' => $order->getOrderNumber(),
            'error' => $errorMessage,
        ]);

        if ($order->getEmail()) {
            $mailerService->sendEmail(
                $order->getEmail(),
                'Echec de paiement - '.$order->getEvent()->getTitle(),
                $this->renderView('email/payment_failed.html.twig', [
                    'order' => $order,
                    'errorMessage' => $errorMessage,
                ]),
            );
        }
    }

    private function handleChargeRefunded(\Stripe\Event $event, EntityManagerInterface $em, MailerService $mailerService, AuditService $audit, BilletOrderService $billetOrderService): void
    {
        $charge = $event->data->object;
        $paymentIntentId = $charge->payment_intent ?? null;

        if (!$paymentIntentId) {
            return;
        }

        $order = $em->getRepository(BilletBuyer::class)->findOneBy(['stripeSessionId' => $paymentIntentId]);
        if (!$order || BilletBuyer::STATUS_REFUNDED === $order->getStatus()) {
            return;
        }

        $amountRefunded = (int) ($charge->amount_refunded ?? 0);
        $previouslyRefunded = $order->getRefundedAmount();
        $newRefundAmount = $amountRefunded - $previouslyRefunded;
        $amountTotal = $order->getTotalHT();
        $order->setRefundedAmount($amountRefunded);

        $isFullRefund = $amountRefunded >= $amountTotal;

        if ($isFullRefund) {
            $order->setStatus(BilletBuyer::STATUS_REFUNDED);

            $tickets = $em->getRepository(BilletOrder::class)->findBy(['billetBuyer' => $order]);
            foreach ($tickets as $ticket) {
                $ticket->setState(BilletOrder::STATE_INVALID);
            }
        } else {
            $order->setStatus(BilletBuyer::STATUS_PARTIALLY_REFUNDED);
        }

        if ($newRefundAmount > 0) {
            $organizer = $order->getEvent()->getAccount();
            $organizer->addDebt($newRefundAmount);
        }

        $em->flush();

        $actionLabel = $isFullRefund ? 'remboursee' : 'partiellement remboursee';

        $audit->log('order_refunded_webhook', 'BilletBuyer', $order->getId(), [
            'orderNumber' => $order->getOrderNumber(),
            'event' => $order->getEvent()->getTitle(),
            'refundedAmount' => $amountRefunded / 100,
            'totalHT' => $order->getTotalHTDecimal(),
            'partial' => !$isFullRefund,
        ]);

        $billetOrderService->notifyOrganizerCancelled($order, $actionLabel);

        if ($order->getEmail()) {
            $mailerService->sendEmail(
                $order->getEmail(),
                'Remboursement - '.$order->getEvent()->getTitle(),
                $this->renderView('email/order_refunded.html.twig', [
                    'order' => $order,
                    'refundedAmount' => $amountRefunded / 100,
                    'isPartial' => !$isFullRefund,
                ]),
            );
        }
    }

    private function handlePayout(\Stripe\Event $event, EntityManagerInterface $em, MailerService $mailerService, PayoutPdfService $pdfService): void
    {
        $payoutData = $event->data->object;
        $payoutId = $payoutData->id ?? null;
        $accountId = $event->account ?? null;

        if (!$payoutId) {
            return;
        }

        $payout = $em->getRepository(Payout::class)->findOneBy(['stripePayoutId' => $payoutId]);
        $user = null;

        if (!$payout) {
            $user = $accountId ? $em->getRepository(User::class)->findOneBy(['stripeAccountId' => $accountId]) : null;

            if (!$user) {
                return;
            }

            $payout = new Payout();
            $payout->setStripePayoutId($payoutId);
            $payout->setStripeAccountId($accountId);
            $payout->setOrganizer($user);

            $em->persist($payout);
        } else {
            $user = $payout->getOrganizer();
        }

        $payout->setStatus((string) ($payoutData->status ?? 'unknown'));
        $payout->setAmount((int) ($payoutData->amount ?? 0));
        $payout->setCurrency((string) ($payoutData->currency ?? 'eur'));
        $payout->setDestination((string) ($payoutData->destination ?? ''));

        if (isset($payoutData->arrival_date) && $payoutData->arrival_date) {
            $payout->setArrivalDate(new \DateTimeImmutable('@'.$payoutData->arrival_date));
        }

        $em->flush();

        $attachments = null;

        if ('paid' === $payout->getStatus()) {
            $pdfPath = $pdfService->generateToFile($payout);
            $attachments = [['path' => $pdfPath, 'name' => 'attestation_'.$payout->getStripePayoutId().'.pdf']];
        }

        $mailerService->sendEmail(
            to: $user->getEmail(),
            subject: sprintf('Virement %s - E-Ticket', strtoupper($payout->getStatus())),
            content: $this->renderView('email/payout_update.html.twig', [
                'firstName' => $user->getFirstName(),
                'payoutId' => $payout->getStripePayoutId(),
                'amount' => number_format($payout->getAmountDecimal(), 2, ',', ' '),
                'currency' => $payout->getCurrency(),
                'status' => $payout->getStatus(),
                'arrivalDate' => $payout->getArrivalDate()?->format('d/m/Y'),
            ]),
            withUnsubscribe: false,
            attachments: $attachments,
        );
    }

    private function handleDisputeCreated(\Stripe\Event $event, EntityManagerInterface $em, MailerService $mailerService): void
    {
        $dispute = $event->data->object;
        $paymentIntentId = $dispute->payment_intent ?? null;
        $amount = (int) ($dispute->amount ?? 0);

        if (!$paymentIntentId || 0 === $amount) {
            return;
        }

        $order = $em->getRepository(BilletBuyer::class)->findOneBy(['stripeSessionId' => $paymentIntentId]);
        if (!$order) {
            return;
        }

        $organizer = $order->getEvent()->getAccount();
        $organizer->addDebt($amount);
        $em->flush();

        $mailerService->sendEmail(
            $organizer->getEmail(),
            'Litige recu - E-Ticket',
            $this->renderView('email/dispute_created.html.twig', [
                'firstName' => $organizer->getFirstName(),
                'amount' => number_format($amount / 100, 2, ',', ' '),
                'orderNumber' => $order->getOrderNumber(),
                'debt' => number_format(($organizer->getDebt() ?? 0) / 100, 2, ',', ' '),
            ]),
        );
    }

    private function handleCheckoutSessionCompleted(\Stripe\Event $event, EntityManagerInterface $em, MailerService $mailerService): void
    {
        $session = $event->data->object;

        if ('subscription' !== ($session->mode ?? null)) {
            return;
        }

        $userId = $session->metadata->user_id ?? null;
        if (!$userId) {
            return;
        }

        $user = $em->getRepository(User::class)->find((int) $userId);
        if (!$user) {
            return;
        }

        $user->setBillingState('good');
        $user->setBillingStripeSubscriptionId($session->subscription ?? null);
        $em->flush();

        $mailerService->sendEmail(
            $user->getEmail(),
            'Abonnement active - E-Ticket',
            $this->renderView('email/billing_activated.html.twig', [
                'firstName' => $user->getFirstName(),
                'amount' => number_format(($user->getBillingAmount() ?? 0) / 100, 2, ',', ' '),
            ]),
        );
    }

    private function handleInvoicePaymentFailed(\Stripe\Event $event, EntityManagerInterface $em, MailerService $mailerService): void
    {
        $invoice = $event->data->object;
        $subscriptionId = $invoice->subscription ?? null;

        if (!$subscriptionId) {
            return;
        }

        $user = $em->getRepository(User::class)->findOneBy(['billingStripeSubscriptionId' => $subscriptionId]);
        if (!$user) {
            return;
        }

        $user->setBillingState('suspendu');
        $this->disableUserEvents($user, $em);
        $em->flush();

        $mailerService->sendEmail(
            $user->getEmail(),
            'Echec de paiement de votre abonnement - E-Ticket',
            $this->renderView('email/billing_failed.html.twig', [
                'firstName' => $user->getFirstName(),
            ]),
        );
    }

    private function handleSubscriptionDeleted(\Stripe\Event $event, EntityManagerInterface $em, MailerService $mailerService): void
    {
        $subscription = $event->data->object;
        $subscriptionId = $subscription->id ?? null;

        if (!$subscriptionId) {
            return;
        }

        $user = $em->getRepository(User::class)->findOneBy(['billingStripeSubscriptionId' => $subscriptionId]);
        if (!$user) {
            return;
        }

        $user->setBillingState('suspendu');
        $this->disableUserEvents($user, $em);
        $em->flush();

        $mailerService->sendEmail(
            $user->getEmail(),
            'Abonnement annule - E-Ticket',
            $this->renderView('email/billing_cancelled.html.twig', [
                'firstName' => $user->getFirstName(),
            ]),
        );
    }

    private function disableUserEvents(User $user, EntityManagerInterface $em): void
    {
        $events = $em->getRepository(Event::class)->findBy(['account' => $user, 'isOnline' => true]);
        foreach ($events as $event) {
            $event->setIsOnline(false);
        }
    }

    /**
     * @param array<string, mixed> $data
     */
    private function findUserByEvent(array $data, EntityManagerInterface $em): ?User
    {
        $accountId = $data['related_object']['id'] ?? null;

        if (!$accountId) {
            return null;
        }

        return $em->getRepository(User::class)->findOneBy(['stripeAccountId' => $accountId]);
    }
}
