<?php

namespace App\Service\Stripe;

use App\Entity\Contrats;
use App\Entity\ContratsPayments;
use App\Entity\Customer;
use App\Entity\StripeConfig;
use App\Entity\Product;
use App\Entity\Options;
use Doctrine\ORM\EntityManagerInterface;
use Stripe\Exception\ApiConnectionException;
use Stripe\Exception\ApiErrorException;
use Stripe\Exception\AuthenticationException;
use Stripe\StripeClient;
use Vich\UploaderBundle\Templating\Helper\UploaderHelper;

class Client
{
    private StripeClient $client;
    private mixed $stripeBaseUrl;

    public function __construct(
        private EntityManagerInterface $em,
        private UploaderHelper $uploaderHelper,
        ?StripeClient $client = null
    ) {
        $stripeSk = $_ENV['STRIPE_SK'] ?? '';
        $this->stripeBaseUrl = $_ENV['STRIPE_BASEURL'] ?? '';
        if($_ENV['APP_ENV'] !== 'test') {
            $this->client = new StripeClient($stripeSk);
        } else {
            $this->client = $client;
        }
    }

    /**
     * Génère l'URL absolue de l'image pour Stripe
     */
    private function getImageUrl($entity): ?array
    {
        $path = $this->uploaderHelper->asset($entity, 'imageFile');
        if (!$path) {
            return null;
        }

        // Construction de l'URL absolue (Stripe nécessite un accès public à l'image)
        $fullUrl = str_starts_with($path, 'http')
            ? $path
            : rtrim($this->stripeBaseUrl, '/') . $path;

        return [$fullUrl];
    }

    /**
     * Vérifie la connexion avec l'API Stripe
     */
    public function check(): array
    {
        try {
            $this->client->accounts->all(['limit' => 1]);
            return ['state' => true, 'message' => 'Connexion établie avec Stripe'];
        } catch (AuthenticationException $e) {
            return ['state' => false, 'message' => 'Clé API Stripe invalide.'];
        } catch (ApiConnectionException) {
            return ['state' => false, 'message' => 'Problème de connexion réseau.'];
        } catch (\Exception $e) {
            return ['state' => false, 'message' => 'Erreur : ' . $e->getMessage()];
        }
    }

    // --- GESTION DES CLIENTS ---

    public function createCustomer(Customer $customer): array
    {
        try {
            $stripeCustomer = $this->client->customers->create([
                'name'  => sprintf('%s %s', $customer->getSurname(), $customer->getName()),
                'email' => $customer->getEmail(),
                'phone' => $customer->getPhone(),
                'metadata' => [
                    'internal_id' => $customer->getId(),
                    'type'        => $customer->getType(),
                ],
                'description' => 'Client synchronisé depuis Ludikevent Intranet',
            ]);

            $customer->setCustomerId($stripeCustomer->id);
            return ['state' => true, 'id' => $stripeCustomer->id];
        } catch (ApiErrorException $e) {
            return ['state' => false, 'message' => $e->getMessage()];
        }
    }

    public function updateCustomer(Customer $customer): array
    {
        if (!$customer->getCustomerId()) return ['state' => false, 'message' => 'Pas d\'ID Stripe'];

        try {
            $this->client->customers->update($customer->getCustomerId(), [
                'name'  => sprintf('%s %s', $customer->getSurname(), $customer->getName()),
                'email' => $customer->getEmail(),
                'phone' => $customer->getPhone(),
                'metadata' => [
                    'internal_id' => $customer->getId(),
                    'updated_at'  => (new \DateTime())->format('Y-m-d H:i:s')
                ],
            ]);
            return ['state' => true, 'message' => 'Client mis à jour'];
        } catch (ApiErrorException $e) {
            return ['state' => false, 'message' => $e->getMessage()];
        }
    }

    public function deleteCustomer(?string $stripeCustomerId): array
    {
        if (!$stripeCustomerId) return ['state' => false];
        try {
            $this->client->customers->delete($stripeCustomerId, []);
            return ['state' => true];
        } catch (ApiErrorException $e) {
            if ($e->getStripeCode() === 'resource_missing') return ['state' => true];
            return ['state' => false, 'message' => $e->getMessage()];
        }
    }

    // --- GESTION DES PRODUITS ---

    public function createProduct(Product $product): array
    {
        try {
            $metadata = ['internal_id' => $product->getId(), 'reference' => $product->getRef()];
            $params = [
                'name'        => $product->getName(),
                'description' => sprintf('REF: %s | CAT: %s', $product->getRef(), $product->getCategory()),
                'metadata'    => $metadata,
                'active'      => true,
            ];

            if ($images = $this->getImageUrl($product)) { $params['images'] = $images; }

            $stripeProduct = $this->client->products->create($params);

            $this->client->prices->create([
                'unit_amount' => (int)($product->getPriceDay() * 100),
                'currency'    => 'eur',
                'product'     => $stripeProduct->id,
            ]);

            $product->setProductId($stripeProduct->id);
            return ['state' => true, 'id' => $stripeProduct->id];
        } catch (\Exception $e) {
            return ['state' => false, 'message' => $e->getMessage()];
        }
    }

    public function updateProduct(Product $product): array
    {
        try {
            $params = [
                'name' => $product->getName(),
                'description' => sprintf('REF: %s | CAT: %s', $product->getRef(), $product->getCategory()),
            ];

            if ($images = $this->getImageUrl($product)) { $params['images'] = $images; }

            $this->client->products->update($product->getProductId(), $params);

            $this->client->prices->create([
                'unit_amount' => (int)($product->getPriceDay() * 100),
                'currency'    => 'eur',
                'product'     => $product->getProductId(),
            ]);

            return ['state' => true, 'message' => 'Produit mis à jour'];
        } catch (\Exception $e) {
            return ['state' => false, 'message' => $e->getMessage()];
        }
    }

    public function deleteProduct(Product $product): array
    {
        if (!$product->getProductId()) return ['state' => true];
        try {
            $this->client->products->update($product->getProductId(), ['active' => false]);
            return ['state' => true];
        } catch (\Exception $e) {
            return ['state' => false, 'message' => $e->getMessage()];
        }
    }

    // --- GESTION DES OPTIONS ---

    public function createOptions(Options $options): array
    {
        try {
            $params = [
                'name'     => $options->getName(),
                'metadata' => ['internal_id' => $options->getId()],
                'active'   => true,
            ];

            if ($images = $this->getImageUrl($options)) { $params['images'] = $images; }

            $stripeProduct = $this->client->products->create($params);

            $this->client->prices->create([
                'unit_amount' => (int)($options->getPriceHt() * 100),
                'currency'    => 'eur',
                'product'     => $stripeProduct->id,
            ]);

            $options->setStripeId($stripeProduct->id);
            return ['state' => true, 'id' => $stripeProduct->id];
        } catch (\Exception $e) {
            return ['state' => false, 'message' => $e->getMessage()];
        }
    }

    public function updateOptions(Options $options): array
    {
        try {
            $params = ['name' => $options->getName()];
            if ($images = $this->getImageUrl($options)) { $params['images'] = $images; }

            $this->client->products->update($options->getStripeId(), $params);

            $this->client->prices->create([
                'unit_amount' => (int)($options->getPriceHt() * 100),
                'currency'    => 'eur',
                'product'     => $options->getStripeId(),
            ]);

            return ['state' => true, 'message' => 'Option mise à jour'];
        } catch (\Exception $e) {
            return ['state' => false, 'message' => $e->getMessage()];
        }
    }

    // --- WEBHOOKS & UTILS ---

    public function webhooks(): array
    {
        $baseUrl = rtrim($this->stripeBaseUrl, '/');
        $configs = [
            'refund' => [
                'url' => $baseUrl . '/webhooks/refund',
                'events' => ['refund.created', 'refund.failed', 'refund.updated']
            ],
            'payment' => [
                'url' => $baseUrl . '/webhooks/payment-intent',
                'events' => ['payment_intent.succeeded', 'payment_intent.canceled','checkout.session.completed']
            ]
        ];

        try {
            $endpoints = $this->client->webhookEndpoints->all();
            foreach ($configs as $name => $config) {
                $stripeEndpoint = null;
                foreach ($endpoints->data as $ep) {
                    if ($ep->url === $config['url']) { $stripeEndpoint = $ep; break; }
                }

                $dbConfig = $this->em->getRepository(StripeConfig::class)->findOneBy(['name' => $name]) ?? new StripeConfig();
                $dbConfig->setName($name);

                if ($stripeEndpoint) {
                    $this->client->webhookEndpoints->update($stripeEndpoint->id, ['enabled_events' => $config['events']]);
                    $dbConfig->setWebhookId($stripeEndpoint->id);
                } else {
                    $new = $this->client->webhookEndpoints->create([
                        'url' => $config['url'],
                        'enabled_events' => $config['events']
                    ]);
                    $dbConfig->setWebhookId($new->id);
                    $dbConfig->setSecret($new->secret);
                }
                $this->em->persist($dbConfig);
            }
            $this->em->flush();
            return ['state' => true];
        } catch (\Exception $e) {
            return ['state' => false, 'message' => $e->getMessage()];
        }
    }


    public function deleteOptions(Options $options): array
    {
        if (!$options->getStripeId()) {
            return ['state' => true];
        }

        try {
            $this->client->products->update($options->getStripeId(), [
                'active' => false,
                'metadata' => [
                    'deleted_at' => (new \DateTime())->format('Y-m-d H:i:s')
                ]
            ]);
            return [
                'state' => true,
                'message' => 'Option désactivée sur Stripe.'
            ];
        } catch (\Exception $e) {
            return ['state' => false, 'message' => $e->getMessage()];
        }
    }

    public function getNativeClient(): StripeClient
    {
        return $this->client;
    }

    public function status(): bool
    {
        return $this->check()['state'];
    }

    /**
     * Récupère le lien existant ou en génère un nouveau si expiré/inexistant
     */
    public function linkPaymentAccompte(float $arrhes, Contrats $contrat, ContratsPayments $contratsPayments): array
    {
        $stripeSessionId = $contratsPayments->getPaymentId();

        // 1. TENTATIVE DE RÉCUPÉRATION DU LIEN EXISTANT
        if ($stripeSessionId) {
            try {
                $session = $this->client->checkout->sessions->retrieve($stripeSessionId);

                // Si la session est active et non payée, on retourne l'URL existante
                if ($session->status === 'open' && $session->payment_status === 'unpaid') {
                    return [
                        'state' => true,
                        'url' => $session->url,
                        'id' => $session->id
                    ];
                }
            } catch (\Exception $e) {
                // Si l'ID n'est pas trouvé chez Stripe, on continue pour en créer un nouveau
            }
        }

        // 2. CRÉATION D'UN NOUVEAU LIEN (Si inexistant, expiré ou invalide)
        $newSession = $this->createPaymentAccompte($arrhes, $contrat);

        if ($newSession['state']) {
            // On met à jour l'entité ContratsPayments avec le nouvel ID de session
            $contratsPayments->setPaymentId($newSession['id']);
            $this->em->persist($contratsPayments);
            $this->em->flush();
        }

        return $newSession;
    }

    /**
     * Ta fonction de création originale (utilisée en fallback)
     */
    public function createPaymentAccompte(float $arrhes, Contrats $contrats): array
    {
        try {
            $session = $this->client->checkout->sessions->create([
                'customer' => $contrats->getCustomer()->getCustomerId(),
                'line_items' => [[
                    'price_data' => [
                        'currency' => 'eur',
                        'product_data' => [
                            'name' => sprintf('Acompte Réservation #%s', $contrats->getNumReservation()),
                            'description' => 'Règlement de 25% pour validation du contrat Ludikevent',
                        ],
                        'unit_amount' => (int)round($arrhes * 100),
                    ],
                    'quantity' => 1,
                ]],
                'mode' => 'payment',
                'success_url' => rtrim($this->stripeBaseUrl, '/') . '/contrat/payment/success/' . $contrats->getId() . '?type=accompte',
                'cancel_url' => rtrim($this->stripeBaseUrl, '/') . '/contrat/payment/cancel/' . $contrats->getId() . '?type=accompte',
                'metadata' => [
                    'contrat_id' => $contrats->getId(),
                    'type' => 'accompte'
                ]
            ]);

            return [
                'state' => true,
                'url' => $session->url,
                'id' => $session->id
            ];
        } catch (\Exception $e) {
            return ['state' => false, 'message' => $e->getMessage()];
        }
    }

    /**
     * Vérifie l'authenticité d'un Webhook Stripe
     * @return bool true si la signature est valide, false sinon
     */
    public function checkWebhooks(\Symfony\Component\HttpFoundation\Request $request, string $configName): bool
    {
        // 1. Récupération de la config (Secret de signature)
        $config = $this->em->getRepository(StripeConfig::class)->findOneBy(['name' => $configName]);

        if (!$config || !$config->getSecret()) {
            return false;
        }

        $payload = $request->getContent();
        $sigHeader = $request->headers->get('Stripe-Signature');


        try {
            // 2. Vérification de la signature via la librairie native Stripe
            // Si la signature est invalide, une exception est levée
            \Stripe\Webhook::constructEvent(
                $payload,
                $sigHeader,
                $config->getSecret()
            );

            return true;
        } catch (\Exception $e) {
            // Signature invalide, timestamp trop ancien, ou payload altéré
            return false;
        }
    }

    public function paymentMethod(mixed $paymentId)
    {
        $data = $this->client->paymentIntents->retrieve($paymentId);
        $paymentMethod = $this->client->paymentMethods->retrieve($data->payment_method);
        return $paymentMethod->toArray();
    }

    public function createPaymentCaution(float|int|null $totalCaution, Contrats $contrat): array
    {
        // On convertit le montant en centimes pour Stripe
        $amountInCents = (int)($totalCaution * 100);

        $session = $this->client->checkout->sessions->create([
            'payment_method_types' => ['card'], // La pré-auth nécessite généralement une carte
            'line_items' => [[
                'price_data' => [
                    'currency' => 'eur',
                    'product_data' => [
                        'name' => 'Dépôt de garantie (Caution) - Réservation #' . $contrat->getNumReservation(),
                        'description' => 'Cette somme sera bloquée temporairement mais non débitée immédiatement.',
                    ],
                    'unit_amount' => $amountInCents,
                ],
                'quantity' => 1,
            ]],
            'mode' => 'payment',
            'payment_intent_data' => [
                'setup_future_usage' => 'off_session',
                'metadata' => [
                    'contrat_id' => $contrat->getId(),
                    'type' => 'caution'
                ],
            ],
            'success_url' => rtrim($this->stripeBaseUrl, '/') . '/contrat/payment/success/' . $contrat->getId() . '?type=caution',
            'cancel_url' => rtrim($this->stripeBaseUrl, '/') . '/contrat/payment/cancel/' . $contrat->getId() . '?type=caution'
        ]);

        return [
            'state' => true,
            'url' => $session->url,
            'id' => $session->id
        ];
    }

    public function linkPaymentCaution(float $arrhes,Contrats $contrat, ContratsPayments $pl)
    {
        $stripeSessionId = $pl->getPaymentId();

        // 1. TENTATIVE DE RÉCUPÉRATION DU LIEN EXISTANT
        if ($stripeSessionId) {
            try {
                $session = $this->client->checkout->sessions->retrieve($stripeSessionId);

                // Si la session est active et non payée, on retourne l'URL existante
                if ($session->status === 'open' && $session->payment_status === 'unpaid') {
                    return [
                        'state' => true,
                        'url' => $session->url,
                        'id' => $session->id
                    ];
                }
            } catch (\Exception $e) {
                // Si l'ID n'est pas trouvé chez Stripe, on continue pour en créer un nouveau
            }
        }

        // 2. CRÉATION D'UN NOUVEAU LIEN (Si inexistant, expiré ou invalide)
        $newSession = $this->createPaymentAccompte($arrhes, $contrat);

        if ($newSession['state']) {
            // On met à jour l'entité ContratsPayments avec le nouvel ID de session
            $pl->setPaymentId($newSession['id']);
            $this->em->persist($pl);
            $this->em->flush();
        }

        return $newSession;
    }

    public function createPaymentSolde(float $finalAmount, Contrats $contrat, bool $isSolde): array
    {
        try {
            // Définition dynamique des textes
            $label = $isSolde ? 'Solde Final' : 'Versement Partiel';
            $type = $isSolde ? 'solde' : 'solde_partiel';

            $session = $this->client->checkout->sessions->create([
                'customer' => $contrat->getCustomer()->getCustomerId(),
                'line_items' => [[
                    'price_data' => [
                        'currency' => 'eur',
                        'product_data' => [
                            'name' => sprintf('%s - Réservation #%s', $label, $contrat->getNumReservation()),
                            'description' => $isSolde
                                ? 'Règlement intégral de votre réservation.'
                                : 'Paiement d\'un acompte complémentaire sur votre solde.',
                        ],
                        'unit_amount' => (int)round($finalAmount * 100),
                    ],
                    'quantity' => 1,
                ]],
                'mode' => 'payment',
                'success_url' => rtrim($this->stripeBaseUrl, '/') . '/contrat/payment/success/' . $contrat->getId() . '?type=solde',
                'cancel_url' => rtrim($this->stripeBaseUrl, '/') . '/contrat/payment/cancel/' . $contrat->getId() . '?type=solde',
                'metadata' => [
                    'contrat_id' => $contrat->getId(),
                    'type' => $type,
                    'amount_requested' => $finalAmount
                ]
            ]);

            return [
                'state' => true,
                'url' => $session->url,
                'id' => $session->id
            ];
        } catch (\Exception $e) {
            return ['state' => false, 'message' => $e->getMessage()];
        }
    }
    public function linkPaymentSolde(float $finalAmount, Contrats $contrat, ContratsPayments $pl, bool $isSolde): array
    {
        $stripeSessionId = $pl->getPaymentId();

        if ($stripeSessionId) {
            try {
                $session = $this->client->checkout->sessions->retrieve($stripeSessionId);
                $amountInCents = (int)round($finalAmount * 100);

                // On vérifie si la session est toujours valide ET correspond au montant saisi
                if ($session->status === 'open' &&
                    $session->payment_status === 'unpaid' &&
                    $session->amount_total === $amountInCents) {
                    $this->client->checkout->sessions->expire($stripeSessionId);
                }
            } catch (\Exception $e) {}
        }

        // Si on doit recréer la session
        $newSession = $this->createPaymentSolde($finalAmount, $contrat, $isSolde);

        if ($newSession['state']) {
            $pl->setPaymentId($newSession['id']);
            $pl->setAmount($finalAmount);
            $pl->setType($isSolde ? 'solde' : 'solde_partiel');
            $this->em->flush();
        }

        return $newSession;
    }

    public function cancelPayment(?string $getPaymentId)
    {
        $session = $this->client->checkout->sessions->retrieve($getPaymentId);
        $paymentIntent = $this->client->paymentIntents->retrieve($session->payment_intent);
        if($paymentIntent->status == "requires_capture") {
            $this->client->paymentIntents->cancel($paymentIntent->id);
            return [
                'state' => true
            ];
        }
        return [
            'state' => false,
            'message' => 'Impossible d\'annuler la caution'
        ];
    }

    public function capture(?string $getPaymentId)
    {
        $session = $this->client->checkout->sessions->retrieve($getPaymentId);
        $paymentIntent = $this->client->paymentIntents->retrieve($session->payment_intent);
        if($paymentIntent->status == "requires_capture") {
            $this->client->paymentIntents->capture($paymentIntent->id);
            return [
                'state' => true
            ];
        }
        return [
            'state' => false,
            'message' => 'Impossible de récupérer la caution'
        ];
    }
}
