<?php

/**
 *  JEGY ADATOK
 * 
 * 'bruttoAr': ha bundle-ben van, akkor hogyan adjak neki árat?
 * 'ervenyessegKezdete': kotelezo megadni, de nem tudom mikor fogja felhasznalni, milyen datum legyen?
 * 
 * bundle jegy kedvezmenyt hogyan kell bekuldeni, mert ott csak a vegosszeg lesz kedvezmenyes, nem tetelenkent
 * 
 * PROGRAM ADATOK
 * 
 * helyszinAdatok: hogy kuldjem be, ha csak buszos/hajos jegyet vesz?
 * programKezdete ertelmezhetetlen - de kotelezo (szamla datuma legyen)
 */
require_once DOL_DOCUMENT_ROOT . '/custom/ntak/class/ntakconnector/ntak_const.class.php';
require_once DOL_DOCUMENT_ROOT . '/custom/ntak/class/ntakconnector/ntak_config.class.php';
require_once DOL_DOCUMENT_ROOT . '/custom/ntak/class/ntakinvoice.class.php';
require_once DOL_DOCUMENT_ROOT . '/custom/ntak/class/ntaksaleschannel.class.php';
require_once DOL_DOCUMENT_ROOT . '/custom/ntak/class/ntakconnector/ntak_date.class.php';
require_once DOL_DOCUMENT_ROOT . '/custom/ntak/class/ntaklogger.class.php';
require_once DOL_DOCUMENT_ROOT . '/custom/ntak/class/ntaksendresult.class.php';
require_once DOL_DOCUMENT_ROOT . '/custom/ntak/class/utils/moneyhandler.class.php';
require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/bbticket.class.php';

class NtakClient
{
    private static $instance = null;
    const CERT_KEY = 'cert.key';
    const CERT_PEM = 'cert.pem';
    const CERT_B64 = 'cert_b64.txt';

    private $date;
    private $postFields;
    private $certDir;

    private NtakInvoice $invoice;
    private NtakLogger $ntakLogger;
    private $NtakConfig;
    private $NTAK_URL;
    private string $REGISTRATION_NUMBER;
    private string $TAX_NUMBER;
    private string $TSS_ID;
    private string $TSS_VERSION;

    public static function getInstance(): NtakClient
    {
        if (self::$instance == null) {
            self::$instance = new NtakClient;
        }

        return self::$instance;
    }
    public function __construct()
    {
        $this->initCertDir();
        $this->checkFiles();

        $this->ntakLogger = NtakLogger::getInstance();
        $this->NtakConfig = new NtakConfig();
        $this->NTAK_URL = $this->NtakConfig->getURL();
        $this->REGISTRATION_NUMBER = $this->NtakConfig->getREGISTRATION_NUMBER();
        $this->TAX_NUMBER = $this->NtakConfig->getTAX_NUMBER();
        $this->TSS_ID = $this->NtakConfig->getTSS_ID();
        $this->TSS_VERSION = $this->NtakConfig->getTSS_VERSION();
    }
    /**
     * 
     */
    private function checkFiles(): void
    {
        $this->checkCertFile(self::CERT_PEM);
        $this->checkCertFile(self::CERT_B64);
    }
    /**
     * 
     */
    private function checkCertFile(string $file): void
    {
        $fullPath = $this->certDir . $file;
        if (file_exists($fullPath)) {
            if (!is_file($fullPath)) {
                throw new \Exception('CERT FILE IS NOT FILE (' . $fullPath . ')');
            }
            if (!is_readable($fullPath)) {
                throw new \Exception('CERT FILE IS NOT READABLE (' . $fullPath . ')');
            }
        } else {
            throw new \Exception('CERT FILE MISSING (' . $fullPath . ')');
        }
    }
    /**
     * 
     */
    private function initCertDir(): void
    {
        $certPath = [
            __DIR__,
            '..',
            '..',
            'cert'
        ];

        $this->certDir = implode(DIRECTORY_SEPARATOR, $certPath) . DIRECTORY_SEPARATOR;
    }
    /**
     * 
     */
    public function sendToServer(array $data): NtakSendResult
    {
        $this->postFields = $data;
        $logData = json_encode($data);
        $invoiceRefs = array_map(function ($item) {
            return $item['tranzakcioAzonosito'];
        }, $data['tranzakcioAdatok']);

        $this->ntakLogger->logIntoFile($logData, LOG_INFO);

        $curl = $this->getCurlResource($this->NTAK_URL . NtakConfig::URL_SALE);

        $response = curl_exec($curl);

/*         if ($response === false) {
            $error = curl_error($curl);  // cURL hiba kiíratása
            $error_no = curl_errno($curl); // Hibakód
            echo "cURL hiba ($error_no): $error";
        } else {
            echo "NTAK válasz: " . $response;
        }
        exit; */

        $this->ntakLogger->logIntoFile($response, LOG_INFO);

        $ntakSendResult = new NtakSendResult;

        if ($response) {
            $resultStatus = 0;
            $result = json_decode($response, true);

            if (!empty($result['feldolgozasAzonosito'])) {
                $ntakSendResult->ntakId = $result['feldolgozasAzonosito'];
                $checkResult = $this->check($result['feldolgozasAzonosito']);

                if (!empty($checkResult['uzenetValaszok'][0]['statusz'])) {
                    $resultStatus = NtakConst::getDbResponseStatusByStatus($checkResult['uzenetValaszok'][0]['statusz']);

                    $ntakSendResult->ntakStatus = $checkResult['uzenetValaszok'][0]['statusz'];
                    $ntakSendResult->status = $resultStatus;
                }

                $this->ntakLogger->logIntoFile("Invoice sent successfully to NTAK (Ref: " . implode(', ', $invoiceRefs) . ")", LOG_INFO);
                $this->ntakLogger->logIntoFile("Invoice data: {$logData}", LOG_INFO);
                $this->ntakLogger->massDbLogByInvoiceRef($invoiceRefs, $resultStatus, curl_error($curl), $logData, $result['feldolgozasAzonosito']);
            } else {
                $this->ntakLogger->logIntoFile("Invoice sent failed to NTAK (Ref: " . implode(', ', $invoiceRefs) . ")", LOG_INFO);
                $this->ntakLogger->logIntoFile($response, LOG_INFO);
                $this->ntakLogger->massDbLogByInvoiceRef($invoiceRefs, $resultStatus, $response, $logData);
            }
        } else {
            $this->ntakLogger->logIntoFile("Cannot send invoice (Ref: " . implode(', ', $invoiceRefs) . ")", LOG_ALERT);
            $this->ntakLogger->massDbLogByInvoiceRef($invoiceRefs, NtakLog::STATUS_CANCELED, curl_error($curl), $logData);
        }

        curl_close($curl);

        return $ntakSendResult;
    }

    /**
     * @var bool $send  If false, not send and return with the generated data
     */
    public function send(NtakInvoice $ntakInvoice, bool $send = true): NtakSendResult
    {
        $this->ntakLogger->logIntoFile("Send invoice to NTAK (Ref: {$ntakInvoice->ref})", LOG_INFO);

        $ntakSendResult = new NtakSendResult;
        $this->invoice = $ntakInvoice;
        $this->date = gmdate(DATE_RFC3339, time());

        $this->initServiceProvider();
        $this->initSenderData();
        $this->initMessageData();
        $this->setPostFieldsProgramData();
        $this->setPostFieldsTransactionData();

        if ($ntakInvoice->array_options['options_ntak_status'] == NtakConst::INVOICE_SENDING_RESPONSE_STATUS_SEND_AGAIN) {
            $origData = $this->ntakLogger->getOrigSentData($ntakInvoice->array_options['options_ntak_id']);
            if (!empty($origData)) {
                $this->postFields = $origData;
            }
        }
        if ($send) {
            //echo json_encode($this->postFields); die;
            //print_r($this->postFields); die;
            $ntakSendResult = $this->sendToServer($this->postFields);
        } else {
            $ntakSendResult->generatedData = $this->postFields;
        }

        return $ntakSendResult;
    }
    /**
     * 
     */
    private function getCurlResource(string $url)
    {
        $curl = curl_init();

        curl_setopt_array(
            $curl,
            [
                CURLOPT_URL => $url,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_SSLCERT => $this->certDir . self::CERT_PEM,
                CURLOPT_SSLKEY => $this->certDir . self::CERT_KEY,
                CURLOPT_ENCODING => '',
                CURLOPT_MAXREDIRS => 10,
                CURLOPT_TIMEOUT => 0,
                CURLOPT_FOLLOWLOCATION => true,
                CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
                CURLOPT_SSL_VERIFYHOST => false,
                CURLOPT_SSL_VERIFYPEER => false,
                CURLOPT_CUSTOMREQUEST => 'POST',
                CURLOPT_POSTFIELDS => json_encode($this->postFields),
                CURLOPT_POST => 1,
                CURLOPT_HTTPHEADER => $this->getHttpHeader(),
            ]
        );
        return $curl;
    }
    /**
     * 
     */
    private function base64url_encode($data)
    {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }
    /**
     * 
     */
    /*
    private function base64url_decode($data) {
        return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
    }
    */
    /**
     * 
     */
    private function setPostFieldsTransactionData(): void
    {
        //$prices = $this->invoice->getRoundedTransactionPrices();
        $prices = $this->invoice->getTotalTransaction();
        $ticketsSold = $this->invoice->ntakSoldTickets();
        $paymentMethods = $this->invoice->ntakPaymentMethods();

        if (!empty($ticketsSold)) {

            $prices = array_sum(array_column($ticketsSold, 'bruttoAr'));

            array_walk($paymentMethods, function (&$item) use ($prices) {
                if (array_key_exists('fizetettOsszegHUF', $item) && $item['fizetesiMod'] == NtakConst::PAYMENT_METHOD_CASH_EUR) {
                    $item['fizetettOsszegHUF'] = $prices;
                }
            });

            $this->postFields['tranzakcioAdatok'] = [
                [
                    'tranzakcioAzonosito' => $this->invoice->ref,
                    'tranzakcioIdopontja' => NtakDate::timestampToRfc3339($this->invoice->date_modification),
                    'osszesitett' => false,
                    'tranzakcioVegosszege' => $prices, //$prices['rounded'],
                    'ertekesitesiCsatorna' => NtakConst::SALES_CHANNEL_LOCAL, // ITT TISZTAZNI, HOGY MIT KELL A MOKUS ES A HOTEL ESETEN
                    'fizetesiModok' => $paymentMethods, //$this->invoice->ntakPaymentMethods(),
                    'latogatoiAdatok' => $this->invoice->ntakCustomerData(),
                    'eladottJegyek' => $ticketsSold, //$this->invoice->ntakSoldTickets(),
                    'egyebTetelek' => $this->invoice->ntakDiscount()
                ]
            ];
        }
    }
    /**
     * 
     */
    private function setPostFieldsProgramData(): void
    {
        $this->postFields['programAdatok'] = $this->invoice->ntakProgramDetails();
    }
    /**
     * 
     */
    private function initMessageData(): void
    {
        $this->postFields['uzenetAdatok'] = [
            'uzenetKuldesIdeje' => $this->date
        ];
    }
    /**
     * 
     */
    private function initSenderData(): void
    {
        $this->postFields['kuldoRendszerAdatok'] = [
            'tssRendszerNTAKAzonosito' => $this->TSS_ID,
            'tssRendszerVerzioszam' => $this->TSS_VERSION
        ];
    }
    /**
     * 
     */
    private function initServiceProvider(): void
    {
        $this->postFields['szolgaltatoAdatok'] = [
            'adoszam' => $this->TAX_NUMBER,
            'szolgHelyRegisztraciosSzam' => $this->REGISTRATION_NUMBER
        ];
    }
    /**
     * 
     */
    private function getHttpHeader(): array
    {
        $header = [
            "alg" => "RS256"
        ];
        $headerBase64URLencodedValue = 'eyJhbGciOiJSUzI1NiJ9';
        // CERT file
        $filenameCert = $this->certDir . self::CERT_B64;
        $handle = fopen($filenameCert, 'r');
        $certificateStr = fread($handle, filesize($filenameCert));
        fclose($handle);

        $headerBaseUrlEnc = json_encode($header);
        $token = $this->base64url_encode($headerBaseUrlEnc) . '.' . $this->base64url_encode(json_encode($this->postFields)); // a json adat maga
        // PEM file
        $filenameSslCert = $this->certDir . self::CERT_KEY;
        $fileContent = file_get_contents($filenameSslCert);
        $privateKey = openssl_pkey_get_private($fileContent);
        openssl_sign("$token", $signature, $privateKey, "sha256");
        $signatureBase64 = $this->base64url_encode($signature);

        $payloadBase64URLencodedValue = '';

        return [
            'Content-Type: application/json',
            'x-jws-signature: ' . $headerBase64URLencodedValue . '.' . $payloadBase64URLencodedValue . '.' . $signatureBase64,
            'x-certificate: ' . $certificateStr
        ];
    }
    /**
     * 
     */
    public function validate(int $ticketId, string $factureRef, string $ntakId, string $productRef, string $productDate, string $logDate, int $factureId): void
    {
        $this->ntakLogger->logIntoFile('Validate start', LOG_INFO);

        $this->date = gmdate(DATE_RFC3339, time());

        $this->initServiceProvider();
        $this->initSenderData();
        $this->initMessageData();

        $this->postFields['jegyek'] = [];

        $this->postFields['jegyek'][] = [
            'ervenyesitesiAzonosito' => $ntakId,
            'belepettSzemelyekSzama' => 1,
            'ervenyesitesIdopontja' => NtakDate::timestampToRfc3339(strtotime($logDate)),
            'felhasznalasModja' => NtakConst::TICKET_USE_METHOD_ENTRY,
            'jegyAzonosito' => 'J-' . $factureRef,
            'programazonosito' => [
                'tssProgramAzonosito' => $productRef,
                'utolsoModositasIdeje' => NtakDate::timestampToRfc3339(strtotime($productDate))
            ]
        ];

        $curl = $this->getCurlResource($this->NTAK_URL . NtakConfig::URL_VALIDATE);
        $response = curl_exec($curl);

        $logData = json_encode($this->postFields);

        if ($response) {
            $result = json_decode($response, true);

            if (array_key_exists('feldolgozasAzonosito', $result)) {
                $this->ntakLogger->logIntoFile("Validate sent successfully to NTAK (Ticket ID: {$ticketId})", LOG_INFO);
            } else {
                $this->ntakLogger->logIntoFile("Validate sent failed to NTAK (Ticket ID: {$ticketId})", LOG_ERR);
                $this->ntakLogger->logIntoFile($logData, LOG_ERR);
                $this->ntakLogger->logIntoFile($response, LOG_ERR);
            }
        } else {
            $this->ntakLogger->logIntoFile("Validate sent failed to NTAK (Ticket ID: {$ticketId})", LOG_ERR);
            $this->ntakLogger->logIntoFile($logData, LOG_ERR);
        }

        curl_close($curl);
    }
    /**
     * 
     */
    public function checkWithLog(string $ntakProcessId, int $invoiceId): array
    {
        $this->ntakLogger->logIntoFile("Check with log start ({$ntakProcessId})", LOG_INFO);

        $resultStatus = NtakLog::STATUS_DRAFT;
        $checkResult = $this->check($ntakProcessId);
        if (!empty($checkResult['uzenetValaszok'][0]['statusz'])) {
            $resultStatus = NtakConst::getDbResponseStatusByStatus($checkResult['uzenetValaszok'][0]['statusz']);
        }

        $this->ntakLogger->logIntoFile("Check successfully at NTAK (NTAK ID: {$ntakProcessId})", LOG_INFO);
        $this->ntakLogger->logIntoDb($invoiceId, $resultStatus, '', '', $ntakProcessId);

        $this->ntakLogger->logIntoFile("Check with log end ({$ntakProcessId})", LOG_INFO);

        return $checkResult;
    }
    /**
     * 
     */
    public function check(string $ntakProcessId): array
    {
        $result = [];

        $this->ntakLogger->logIntoFile("Check sent data ({$ntakProcessId})", LOG_INFO);

        $this->postFields = [];
        $this->date = gmdate(DATE_RFC3339, time());

        $this->initServiceProvider();
        $this->initSenderData();
        $this->initMessageData();

        $this->postFields['feldolgozasAzonositok'] = [
            $ntakProcessId
        ];

        $this->ntakLogger->logIntoFile(json_encode($this->postFields), LOG_INFO);

        $curl = $this->getCurlResource($this->NTAK_URL . NtakConfig::URL_CHECK);
        $response = curl_exec($curl);
        if ($response) {
            $result = json_decode($response, true);
            $this->ntakLogger->logIntoFile("Check invoice process at NTAK (NTAK process ID: {$ntakProcessId})", LOG_INFO);
            $this->ntakLogger->logIntoFile($response, LOG_INFO);
        } else {
            $this->ntakLogger->logIntoFile("Cannot check invoice process at NTAK (NTAK process ID: {$ntakProcessId})", LOG_ALERT);
        }

        curl_close($curl);

        return $result;
    }
}
