<?php

/*
 * This file is part of the VitiCore 2.
 *
 * (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\Payment;

use CpCreation\VitiCore\Behavior\HasPayment;
use CpCreation\VitiCore\Cart\Model\Cart;
use CpCreation\VitiCore\Cart\Model\Payment\PayPlugModel;
use CpCreation\VitiCore\Cart\Repository\PaymentMethodRepository;
use Payplug\Payplug;
use Payplug\Resource\Payment;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Serializer\SerializerInterface;

final class PayPlugPayment
{
    public const METHOD = 'payplug';
    public const CURRENCY = 'EUR';
    public const API_VERSION = '2019-08-06';

    /** @var array<string, mixed> */
    private array $paths;

    private ?PayPlugModel $config = null;
    private ?Payplug $client = null;

    public function __construct(
        private readonly PaymentMethodRepository $paymentMethodRepository,
        private readonly SerializerInterface $serializer,
        private readonly RouterInterface $router,
        array $paths,
    ) {
        $this->paths = $paths;
    }

    private function boot(): void
    {
        if (null !== $this->config && null !== $this->client) {
            return;
        }

        $paymentMethod = $this->paymentMethodRepository->findOneBy(['method' => self::METHOD]);
        if (!$paymentMethod || !$paymentMethod->getConfiguration()) {
            throw new \RuntimeException('Payment method "payplug" non configuré.');
        }

        /** @var PayPlugModel $config */
        $config = $this->serializer->denormalize($paymentMethod->getConfiguration(), PayPlugModel::class);
        $this->config = $config;

        // Client Payplug utilisé par retrieve()
        $this->client = new Payplug($config->getPrivateKey());

        // Init globale pour Payment::create()
        Payplug::init([
            'secretKey' => $config->getPrivateKey(),
            'apiVersion' => self::API_VERSION,
        ]);
    }

    /**
     * Crée un paiement Payplug (hosted payment) et renvoie l'objet Payment Payplug.
     */
    public function createData(HasPayment $payment): Payment
    {
        $this->boot();

        $doneRoute = $this->paths['payplug']['done'] ?? null;
        $refusedRoute = $this->paths['payplug']['refused'] ?? null;
        $responseAt = $this->paths['payplug']['response_at'] ?? null;

        if (!\is_string($doneRoute) || '' === $doneRoute) {
            throw new \RuntimeException('Route paths[payplug][done] manquante.');
        }
        if (!\is_string($refusedRoute) || '' === $refusedRoute) {
            throw new \RuntimeException('Route paths[payplug][refused] manquante.');
        }
        if (!\is_string($responseAt) || '' === $responseAt) {
            throw new \RuntimeException('Route paths[payplug][response_at] manquante.');
        }

        $returnParameters = $this->buildReturnParameters($payment);

        $returnUrl = $this->router->generate($doneRoute, $returnParameters, UrlGeneratorInterface::ABSOLUTE_URL);
        $cancelUrl = $this->router->generate($refusedRoute, $returnParameters, UrlGeneratorInterface::ABSOLUTE_URL);
        $notifyUrl = $this->router->generate($responseAt, [], UrlGeneratorInterface::ABSOLUTE_URL);

        $data = [
            'amount' => $payment->getTotal(), // en centimes
            'currency' => self::CURRENCY,
            'save_card' => false,
            'billing' => [
                'title' => null,
                'first_name' => (string) $payment->getFirstname(),
                'last_name' => (string) $payment->getLastname(),
                'mobile_phone_number' => null,
                'email' => (string) $payment->getEmail(),
                'address1' => (string) $payment->getStreet(),
                'postcode' => (string) $payment->getPostalCode(),
                'city' => (string) $payment->getCity(),
                'country' => (string) $payment->getCountry(),
            ],
            'shipping' => [
                'title' => null,
                'first_name' => (string) $payment->getFirstname(),
                'last_name' => (string) $payment->getLastname(),
                'mobile_phone_number' => null,
                'email' => (string) $payment->getEmail(),
                'address1' => (string) $payment->getStreet(),
                'postcode' => (string) $payment->getPostalCode(),
                'city' => (string) $payment->getCity(),
                'country' => (string) $payment->getCountry(),
            ],
            'hosted_payment' => [
                'return_url' => $returnUrl,
                'cancel_url' => $cancelUrl,
            ],
            'notification_url' => $notifyUrl,
            'metadata' => [
                'transaction_id' => (string) $payment->getToken(),
            ],
        ];

        return Payment::create($data);
    }

    /**
     * @return array<string, string>
     */
    private function buildReturnParameters(HasPayment $payment): array
    {
        if ($payment instanceof Cart) {
            return ['cartId' => $payment->getId()->toString()];
        }

        return [];
    }

    /**
     * Retourne les infos “fiables” du paiement Payplug à partir d'un payload webhook / API.
     *
     * @param array{id:string} $paymentArray
     *
     * @return array<string, mixed>
     */
    public function valid(array $paymentArray): array
    {
        $this->boot();

        if (!isset($paymentArray['id']) || !\is_string($paymentArray['id']) || '' === $paymentArray['id']) {
            throw new \InvalidArgumentException('Payplug: champ "id" manquant.');
        }

        // On re-récupère côté Payplug (source de vérité)
        $resourcePayment = Payment::retrieve($paymentArray['id'], $this->client);

        // ✅ Option 1 (souvent OK): l'objet est jsonSerializable
        $json = json_encode($resourcePayment, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES);
        if (false !== $json) {
            $decoded = json_decode($json, true);
            if (\is_array($decoded)) {
                return $decoded;
            }
        }

        // ✅ Option 2: fallback minimal via getters usuels
        // (si ta version de lib ne supporte pas jsonSerialize)
        return [
            'id' => method_exists($resourcePayment, 'getId') ? $resourcePayment->getId() : $paymentArray['id'],
            'is_paid' => method_exists($resourcePayment, 'isPaid') ? $resourcePayment->isPaid() : null,
            'amount' => method_exists($resourcePayment, 'getAmount') ? $resourcePayment->getAmount() : null,
            'currency' => method_exists($resourcePayment, 'getCurrency') ? $resourcePayment->getCurrency() : null,
            'metadata' => method_exists($resourcePayment, 'getMetadata') ? $resourcePayment->getMetadata() : null,
            'status' => method_exists($resourcePayment, 'getStatus') ? $resourcePayment->getStatus() : null,
            'created_at' => method_exists($resourcePayment, 'getCreatedAt') ? $resourcePayment->getCreatedAt() : null,
        ];
    }
}
