<?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\Bridge\Symfony\Bundle\Utils\Mailer;
use CpCreation\VitiCore\Cart\Model\Cart;
use CpCreation\VitiCore\Cart\Model\Payment\Monetico;
use CpCreation\VitiCore\Cart\Repository\PaymentMethodRepository;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Serializer\SerializerInterface;

final class MoneticoPayment
{
    public const METHOD = 'monetico';
    public const TEST_URL = 'https://p.monetico-services.com/test/paiement.cgi';
    public const PROD_URL = 'https://p.monetico-services.com/paiement.cgi';
    public const DEVISE = 'EUR';

    public const OPTIONS = '';
    public const CTLHMAC = 'V4.0.sha1.php--[CtlHmac%s%s]-%s';
    public const CTLHMACSTR = 'CtlHmac%s%s';
    public const PHASE2BACK_RECEIPT = "version=2\ncdr=%s";
    public const PHASE2BACK_FIELDS = '%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*';
    public const PHASE1GO_FIELDS = '%s*%s*%s*%s%s*%s*%s*%s*%s*%s';

    public const NBR_ECH = '';
    public const DATE_ECH_1 = '';
    public const MONTANT_ECH_1 = '';
    public const DATE_ECH_2 = '';
    public const MONTANT_ECH_2 = '';
    public const DATE_ECH_3 = '';
    public const MONTANT_ECH_3 = '';
    public const DATE_ECH_4 = '';
    public const MONTANT_ECH_4 = '';

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

    private ?Monetico $config = null;

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

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

        $paymentMethod = $this->paymentMethodRepository->findOneBy(['method' => self::METHOD]);
        if (!$paymentMethod) {
            throw new \RuntimeException('Payment method "monetico" introuvable.');
        }

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

    /**
     * @return array<string, mixed>
     */
    public function createData(HasPayment $payment): array
    {
        $this->boot();
        $returnParameters = $this->buildReturnParameters($payment);

        $total = $payment->getTotal() / 100; // comme ton code: cents -> euros
        $locale = 'FR';
        // si HasPayment a getLocale() ok, sinon fallback:
        if (method_exists($payment, 'getLocale') && $payment->getLocale()) {
            $locale = (string) $payment->getLocale();
        }

        $reference = mb_strtoupper((string) $payment->getToken());

        $canceledRoute = $this->paths['monetico']['canceled'] ?? null;
        $doneRoute = $this->paths['monetico']['done'] ?? null;
        $refusedRoute = $this->paths['monetico']['refused'] ?? null;

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

        $urlRetour = $this->router->generate($canceledRoute, $returnParameters, UrlGeneratorInterface::ABSOLUTE_URL);
        $urlRetourOk = $this->router->generate($doneRoute, $returnParameters, UrlGeneratorInterface::ABSOLUTE_URL);
        $urlRetourErr = $this->router->generate($refusedRoute, $returnParameters, UrlGeneratorInterface::ABSOLUTE_URL);

        $billing = [];
        if ($payment->getCompany()) {
            $billing['name'] = (string) $payment->getCompany();
        }
        $billing = array_merge($billing, [
            'firstName' => mb_substr((string) $payment->getFirstname(), 0, 44),
            'lastName' => mb_substr((string) $payment->getLastname(), 0, 44),
            'addressLine1' => mb_substr((string) $payment->getStreet(), 0, 48),
            'city' => (string) $payment->getCity(),
            'postalCode' => (string) $payment->getPostalCode(),
            'country' => (string) $payment->getCountry(),
        ]);

        $contexteCommande = [
            'billing' => $billing,
        ];

        // IMPORTANT : tu fais base64_encode(utf8_encode(json_encode()))
        // On garde, même si utf8_encode est discutable (ISO-8859-1 -> UTF-8).
        $contexteJson = json_encode($contexteCommande, \JSON_UNESCAPED_UNICODE);
        if (false === $contexteJson) {
            $contexteJson = '{}';
        }

        $data = [
            'TPE' => $this->config->getEptNumber(),
            'societe' => $this->config->getCompanyCode(),
            'lgue' => mb_strtoupper($locale),
            'version' => $this->config->getVersion(),
            'reference' => $reference,
            'date' => (new \DateTimeImmutable())->format('d/m/Y:H:i:s'),
            'montant' => $total.self::DEVISE,
            'contexte_commande' => base64_encode(utf8_encode($contexteJson)),
            'mail' => (string) $payment->getEmail(),
            'url_retour' => $urlRetour,
            'url_retour_ok' => $urlRetourOk,
            'url_retour_err' => $urlRetourErr,
        ];

        // Monetico: signature sur les champs triés par clé
        ksort($data);

        $formFields = $data;
        array_walk($formFields, static function (&$item, $key) {
            $item = $key.'='.$item;
        });

        $msg = implode('*', $formFields);

        $data['URL'] = $this->config->isProduction() ? self::PROD_URL : self::TEST_URL;
        $data['MAC'] = $this->computeHmac($msg, (string) $this->config->getHmacKey());

        return $data;
    }

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

        return [];
    }

    public function valid(?array $response = null): bool
    {
        if (!$response) {
            return false;
        }

        $this->boot();

        $mac = isset($response['MAC']) ? (string) $response['MAC'] : '';
        // champs parfois absents
        $response['texte-libre'] = $response['texte-libre'] ?? '';
        $response['motifrefus'] = $response['motifrefus'] ?? '';

        $required = [
            'TPE', 'date', 'contexte_commande', 'montant', 'reference',
            'texte-libre', 'code-retour', 'cvx', 'vld', 'brand', 'status3ds',
            'numauto', 'motifrefus', 'originecb', 'bincb', 'hpancb',
            'ipclient', 'originetr', 'veres', 'pares',
        ];

        foreach ($required as $field) {
            if (!\array_key_exists($field, $response)) {
                $this->mailer->sendErrorValidReturnPayment(json_encode($response, \JSON_UNESCAPED_UNICODE));

                // ⚠️ Je garde ton comportement legacy: "on log et on retourne true"
                return true;
            }
        }

        $msgBack = \sprintf(
            self::PHASE2BACK_FIELDS,
            $response['TPE'],
            $response['contexte_commande'],
            $response['date'],
            $response['montant'],
            $response['reference'],
            $response['texte-libre'],
            $this->config->getVersion(),
            $response['code-retour'],
            $response['cvx'],
            $response['vld'],
            $response['brand'],
            $response['status3ds'],
            $response['numauto'],
            $response['motifrefus'],
            $response['originecb'],
            $response['bincb'],
            $response['hpancb'],
            $response['ipclient'],
            $response['originetr'],
            $response['veres'],
            $response['pares']
        );

        $computed = $this->computeHmac($msgBack, (string) $this->config->getHmacKey());

        if ($computed === mb_strtolower($mac)) {
            return true;
        }

        $this->mailer->sendErrorValidReturnPayment(json_encode($response, \JSON_UNESCAPED_UNICODE));

        // ⚠️ Comportement legacy : true même si MAC invalide (tu “log” seulement)
        return true;
    }

    private function getUsableKey(string $key): string
    {
        $hexStrKey = mb_substr($key, 0, 38);
        $hexFinal = mb_substr($key, 38, 2).'00';

        $cca0 = \ord($hexFinal);

        if ($cca0 > 70 && $cca0 < 97) {
            $hexStrKey .= \chr($cca0 - 23).mb_substr($hexFinal, 1, 1);
        } else {
            if ('M' === mb_substr($hexFinal, 1, 1)) {
                $hexStrKey .= mb_substr($hexFinal, 0, 1).'0';
            } else {
                $hexStrKey .= mb_substr($hexFinal, 0, 2);
            }
        }

        return $hexStrKey;
    }

    public function computeHmac(string $data, string $hmacKey): string
    {
        return mb_strtolower(hash_hmac('sha1', $data, hex2bin($this->getUsableKey($hmacKey))));
    }
}
