<?php


namespace App\Services\MobileMoney;

use App\Exceptions\ServerStatus;
use App\Models\PaymentManagement\Payment;
use App\Models\PaymentManagement\PaymentMethod;
use App\Models\SettingManagement\Status;
use App\Models\SettingManagement\PaymentTransactionStatus;
use App\Services\MobileMoney\MTN\MTNMobileMoneyCollection;
use App\Services\MobileMoney\Orange\OrangeMobileMoney;
use App\Services\MobileMoney\PVIT\Pvit;
use App\Models\PaymentManagement\PaymentTransaction;
use App\Services\OnlinePayments\PayPalRest;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Throwable;

trait MakeAMobileMoney{

    /** Enregistre la transaction de paiement en bd
     * @param $payer
     * @param array $datas
     * @return PaymentTransaction
     */
    public function createTransaction($payer,$datas){
        return $payer->transactions()->create($datas);
    }

    /** Alerte HTML affiché au client après que la transaction ait été initié
     * @param array $config
     * @return string
     */
    protected function getWaitingAlertHtml($config){
        $countDownMessageTitle = __('Time since Request for Payment:');
        $alertMessage = __($config['message']) ?? __("Veuillez patienter ...");
        $alertStyle = $config['style'] ?? "";
        return "<div class='card-body' style='border-radius: 3px; text-align: center; padding:2px;  border: 1px solid #eee; $alertStyle'>
                <div style='text-align: center; margin-bottom 0; line-height: 23px; font-size: 13px; '>
                    <span>$alertMessage </span> <br>
                    <span>$countDownMessageTitle</span> <br>
                    <mark id='countdown' style='background: #fff; color: #e74a3b !important;'>
                          <span class='counter-text' id='countdown-minute' >00</span> min
                          <span class='counter-text' id='countdown-second' >00</span> sec
                    </mark>
                </div>
            </div>";
    }

    /**
     * @param Request $request
     * @param array $datasTransaction Les données a enregistré pour la transaction
     * @param Model $payer Le payeur
     * @return array|JsonResponse
     * @throws GuzzleException
     */
    public function momoInitTransaction(Request $request, $datasTransaction = [],$payer = null){
        $paymentMethodId = $request->paymentMethodId;
        $paymentMethod = PaymentMethod::findOrFail($paymentMethodId);

        /*code methode de payment*/
        $paymentMethodCode = $paymentMethod->code; // ["mtn-momo-cmr"]

        try {
            $momoConfig = config("mobile-money.$paymentMethodCode");

            /*configs validation du numéro de télephone*/
            $validationPhoneNumber = $momoConfig["validation"];

            /*montant du payment*/
            $amount = (int) ceil($request->amount); // "3000"
            // $amount = 100;

            /*numero de telephone du payeur/client */
            $phoneNumber  = $request->phoneNumber; // "683523318"

            $prefix = $validationPhoneNumber['prefix']; // "237"
            $max_lenght_without_prefix = $validationPhoneNumber['max_lenght_without_prefix']; // "9"
            $max_lenght_with_prefix = $validationPhoneNumber['max_lenght_with_prefix']; // "12"

            /*possible prefix de l'operateur dans le numéro saisi par le client: (les 03 premiers caractères du numéro)*/
            $possiblePrefix = substr($phoneNumber,0,3); // "683"

            //longueur du numéro de téléphone saisi
            $phoneNumberLenght = strlen($phoneNumber); // "9"
            $status = null;

            $result = [];
            $success = false;
            $waiting_alert = "";
            $errorMessage = "";
            $transactionReference = "";

//        dd($phoneNumberLenght,$max_lenght_with_prefix,$max_lenght_with_prefix);
            if ( !in_array($phoneNumberLenght,[$max_lenght_without_prefix, $max_lenght_with_prefix])){
                $errorMessage = __("Votre Nº de téléphone doit contenir ':min' chiffres
                            sans prefix ':prefix' ou ':max' chiffres avec le prefix ':prefix' .",
                    [ "min" => $max_lenght_without_prefix, "max" => $max_lenght_with_prefix, "prefix" => $prefix ]);
            }
            else{ // numéro de télephone valide

                $payerPhoneNumber = $phoneNumber;
                if (in_array($paymentMethodCode,["mtn-momo-cmr","orange-momo-cmr"])){
                    /*formatage du numero de telephone au format accepter par l'api*/
                    if ($phoneNumberLenght != $max_lenght_with_prefix) {
                        $payerPhoneNumber = $prefix.$phoneNumber; // [237683523318]
                    }
                }
                else{ // "PVIT"
                    /*si le numero comporte l'prefix ex 241683523318*/
                    if ($phoneNumberLenght == $max_lenght_with_prefix) {
                        /*formate le numero de telephone au format accepter par l'api 0683523318*/
                        $phoneNumber = str_replace($possiblePrefix,"",$phoneNumber);
                    }
                    $payerPhoneNumber = (substr($phoneNumber,0,1) == "0") ? $phoneNumber:"0".$phoneNumber;
                }

                $order_reference = "";
                //si l'operateur a un préfix pour refrence
                //if (isset($momoConfig['operator_code']) and !empty($momoConfig['operator_code'])){
                //$order_reference = new_payment_reference($momoConfig['operator_code']);
                //}

                switch ($paymentMethodCode){
                    case "mtn-momo-cmr":
                        $mtnMomoCollection = new MTNMobileMoneyCollection($paymentMethod);
                        //requete à l'api pour validation du numéro saisi
                        $active =   $mtnMomoCollection->isActive($payerPhoneNumber) ?? false;

                        if (is_bool($active)){
                            if ($active){  // si le numero est actif
                                $transactionMessage = __("Merci de nous faire confiance");
                                $transactionNote = __("Nouveau payment");
                                $externalId =  $payerPhoneNumber;

                                /*si le montant du payment est > 0 et le tel  n'est pas vide*/
                                if ($amount > 0 and !empty($payerPhoneNumber)){
                                    /*inititiation du payment MTN Momo et retour d'un  (string uuid4) [XReferenceId] */
                                    $transactionReference = $mtnMomoCollection->requestToPay(
                                                                    $externalId,
                                                                    $payerPhoneNumber,
                                                                    $amount,
                                                                    $transactionMessage,
                                                                    $transactionNote
                                                            );

                                    $status = 200;
                                }else{
                                    $errorMessage = __("ERREUR FATALE LORS DE L'INITIATION DE LA TRANSACTION : MONTANT TRANSACTION INVALIDE");
                                }
                            }else{
                                $errorMessage = __("Vous avez fourni un numéro de téléphone incorrect.");
                            }
                        }
                        break ;

                    case "orange-momo-cmr" :
                        $orangeMomo = new OrangeMobileMoney($paymentMethod);
                        $transactionReference = $orangeMomo->getPayToken();
                        $message_push = " ".__("Nouvelle commmande ");
                        $amount = 100 ;
                        $datas_transact = [
                            "phoneNumber"   => $payerPhoneNumber,
                            "amount"   => $amount,
                            "order_ref" => $order_reference,
                            "message" => $message_push,
                            "pay_token" => $transactionReference,
                        ];

                        $response_transaction = $orangeMomo->transact($datas_transact) ?? null;
                        $status = $response_transaction['inittxnstatus'] ?? null;

                    //    dd($status,$response_transaction);
                        break;

                    case "mobi-cash-gab" or "airtel-momo-gab":

                        $pvit = new Pvit($momoConfig['tel_marchand'],$momoConfig['operator_code']);

                        $refPaiement = $order_reference;

                        $parametres = [
                            "tel_client" => $payerPhoneNumber, //tel du client
                            "montant" => $amount, // montant du payment
                            "ref" => $refPaiement, //ref du payment
                            "agent" => $payerPhoneNumber, //agent
                        ];


                        /*initie la transaction et retourne une reponse xml formater en objet*/
                        $response_transaction =  $pvit->parseResponse($pvit->transact($parametres)); // (object)

                        $fichier_reponse = $pvit->get_path_file_response_statut($refPaiement);

                        /*si le fichier existe ou que la reponse de la transaction n'est pas vide*/
                        if (file_exists($fichier_reponse) or !empty($response_transaction)){

                            /*si le fichier existe*/
                            if (file_exists($fichier_reponse)){
                                $response_transaction =  (object) json_decode(file_get_contents($fichier_reponse),true);
                                $status = 200;
                            }
                            /*recupere le statut de la demande de payment*/
                            $status = $response_transaction->STATUT ?? null;

                            $errorMessage = $response_transaction->MESSAGE ?? __("Erreur fatale du système, veuillez reéssayer");

                            $transactionReference = $refPaiement;
                        }
                        break;

                }

                if (!empty($transactionReference)){
                    $datasTransaction['payment_method_id'] = $paymentMethodId;
                    $datasTransaction['reference'] = $transactionReference;
                    $datasTransaction['amount'] = $amount;
                    $datasTransaction['payer_number'] = $payerPhoneNumber;
                    //sauvegarde la transaction
                    $this->createTransaction($payer,$datasTransaction,$payer);
                }

                /*si la transaction a été inité avec succès et que l'api a retourné une référence de transaction*/
                if ( ($status == "200" or $status == 200) and  !empty($transactionReference) ){

                    /*durée maximale de la transaction*/
                    $timeout_transaction = $momoConfig['timeout_transaction'];

                    /*période de vérification du statut de la transaction*/
                    $time_check_status = $momoConfig['time_check_status'];

                    /*alerte html d'attente de validation*/
                    $waiting_alert = $this->getWaitingAlertHtml($momoConfig['waiting_alert']);

                    $success = true;
                    $result['transaction'] = [
                        "waiting_alert" => $waiting_alert,
                        "waiting_message" => $momoConfig['waiting_alert']["message"] ?? __("La transaction a été initié"),
                        "reference" => $transactionReference,
                        "timeout" => $timeout_transaction,
                        "time_check_status" => $time_check_status,
                    ];
                    $errorMessage = null;

                }else{
                    $op = $paymentMethod->name;
                    $errorMessage = __("La transaction n'a pas été initié. les raisons peuvent être les suivantes")." :<br>"
                        .__("1- Vous avez saisi un Nº :op. incorrect, <br>", ["op" => $op])
                        .__("2- Votre solde :op. est insuffisant pour valider l'opération. <br>", ["op" => $op])
                        ." ".__("Verifiez et reéssayez.");
                }
            }

            $result["success"] = $success;
            $result["message"] = $errorMessage;
            return $result;
        }catch (\Exception $exception){

            write_log("momo/init/$paymentMethodCode",$exception,"error");
            return response()->json([
                "success" => false,
                "message" => general_error($exception),
            ],500);
        }
    }

    public function initTransaction(Request $request, $datasTransaction = [],$payer = null){
        $paymentMethodId = $request->paymentMethodId;
        $paymentMethod = PaymentMethod::findOrFail($paymentMethodId);

        /*code methode de payment*/
        $paymentMethodCode = $paymentMethod->code; // ["mtn-momo-cmr"]

        try {
            $momoConfig = config("mobile-money.$paymentMethodCode");

            if (in_array($paymentMethod->code,['mtn-momo-cmr',"orange-momo-cmr"])){
                return $this->momoInitTransaction($request, $datasTransaction, $payer);
            }

            /*montant du payment*/
            $amount = (int) ceil($request->amount); // "3000"
            $status = null;

            $result = [];
            $success = false;
            $waiting_alert = "";
            $errorMessage = "";
            $transactionReference = "";

            switch ($paymentMethodCode){


                case "paypal-rest" :
                    $paypal = new PayPalRest($paymentMethod);

                    $exchange_value =  getSettingsOf("exchange_value") ?? 1;
                    $paypalAmount =  $amount / $exchange_value;

                    $params = [
                        "amount" => round($paypalAmount,2) ,
                        "currency" => env("PAYPAL_CURRENCY","EUR"),
                        "returnUrl" => route("gateway.payment.paypal-rest.success",$paymentMethod),
                        "cancelUrl" => route("gateway.payment.paypal-rest.cancel"),
                    ];

                    $response  = $paypal->purchase($params);
                    $data = $response->getData();
                    $transactionReference = $data['id'];
                    $status = $response->getCode();
                    $result['callback_url'] = $response->getRedirectUrl();

                    break;

            }

            if (!empty($transactionReference)){
                $datasTransaction['payment_method_id'] = $paymentMethodId;
                $datasTransaction['amount'] = $amount;
                $datasTransaction['reference'] = $transactionReference;
                //sauvegarde la transaction
                $this->createTransaction($payer,$datasTransaction,$payer);
            }

            /*si la transaction a été inité avec succès et que l'api a retourné une référence de transaction*/
            if ( ( $status == 201) and  !empty($transactionReference) ){

                /*durée maximale de la transaction*/
                $timeout_transaction = $momoConfig['timeout_transaction'];

                /*période de vérification du statut de la transaction*/
                $time_check_status = $momoConfig['time_check_status'];

                /*alerte html d'attente de validation*/
                $waiting_alert = $this->getWaitingAlertHtml($momoConfig['waiting_alert']);

                $success = true;
                $result['transaction'] = [
                    "waiting_alert" => $waiting_alert,
                    "waiting_message" => $momoConfig['waiting_alert']["message"] ?? __("La transaction a été initié"),
                    "reference" => $transactionReference,
                    "timeout" => $timeout_transaction,
                    "time_check_status" => $time_check_status,
                ];
                $errorMessage = "";

            }else{
                $errorMessage = __("La transaction n'a pas été initié. Réessayer plus tard");
            }

            $result["success"] = $success;
            $result["message"] = $errorMessage;
            return $result;
        }catch (\Exception $exception){

            write_log("momo/init/$paymentMethodCode",$exception,"error");
            return response()->json([
                "success" => false,
                "message" => general_error($exception),
            ],ServerStatus::APPLICATION_ERROR);
        }
    }

    public function possiblesStatusesForMTNOrOrange(){
        return [
            "SUCCESSFUL" => [
                "message" => __("Nous avons reçus votre paiement")." ",
                "success" => true
            ],

            "PENDING" =>  ["message" => __("En attente de validation.")],
            "TIMEOUT" =>  ["message" => __("Le temps de validation de votre payment a expiré.")],
            "REJECTED" => ["message" => __("La transaction a été brutalement rejété")],

            "FAILED" => [
                "message" => __("Votre transaction a été annulé. Raisons possibles:"),
                "reasons" => [
                    __("1- Votre solde est insuffisant ,"),
                    __("2- Vous avez mis trop de temps pour valider la transaction."),
                ]
            ],
        ];

    }

    /**
     * @return array
     */
    public function pvitPossiblesStatuses() {
        return [
            "200" => [
                "message" => __("Nous avons reçu votre paiement."),
                "success" => true
            ],
            "0"   =>   [ "message" => __("Echec d’initialisation de la transaction.") ],
            "001" =>   [ "message" => __("Paramètres incorrects") ],
            "002" =>   [ "message" => __("Le temps de validation de votre payment a expiré.") ],
            "003" =>   [ "message" => __("Marchand inconnu.") ],
            "004" =>   [ "message" => __("Erreur de barème des commissions.") ],
            "005" =>   [ "message" => __("Le marchand a une tâche en cours.") ],
            "006" =>   [ "message" => __("Erreur N° téléphone du client.") ],
            "007" =>   [ "message" => __("Erreur Token, Token invalide.") ],
        ];
    }

    /** Retourne le status d'une transaction
     * @param PaymentTransaction $transaction
     * @return array|mixed|null
     * @throws GuzzleException
     */
    public function getTransactionStatus (PaymentTransaction $transaction){
        $transactionReference = $transaction->reference;
        $paymentMethod = $transaction->paymentMethod;
        $paymentMethodCode = $paymentMethod->code;
        $response = null;

        switch ($paymentMethodCode){

            case 'mtn-momo-cmr':
                $mtnMomoCollection = new MTNMobileMoneyCollection($paymentMethod);
                $response = $mtnMomoCollection->getTransactionStatus($transactionReference);
                break;

            case 'orange-momo-cmr' :
                $orangeMomo = new OrangeMobileMoney($paymentMethod);
                $response = $orangeMomo->getTransactStatus($transactionReference);
                break;

            case 'paypal-rest':
                $paypal = new PayPalRest($paymentMethod);
                $response = $paypal->getTransactionStatus($transaction->file);
                break;

            case 'mobi-cash-gab' or 'airtel-momo-gab' :
                $momoConfig = config("mobile-money.$paymentMethodCode");
                $pvit = new Pvit($momoConfig['tel_marchand'],$momoConfig['operator_code']);

                $chemin = $pvit->get_path_file_response_statut($transactionReference); // chemin du fichier reponse

                /*si le fichier de reponse existe => l'api déjà repondu via le callback fournie*/
                if (file_exists($chemin)){
                    //retourne le contenu du fichier sous forme de tableau clef=>valeur
                    $response = json_decode(file_get_contents($chemin),true);
                }else{
                    //interroge le statut en utilisant la requete PVIT et le retourne sous forme de tableau clef=>valeur
                    //dans la majorité des cas retournera l'index STATUT => '008'
                    $response = (array) $pvit->parseResponse($pvit->get_statut_transact($transactionReference));
                }
                break;
        }
        return $response;
    }

    /** Check le status d'une transaction
     * @param $response
     * @param PaymentTransaction $transaction
     * @param array $paymentDatas
     * @return array|JsonResponse
     * @throws Throwable
     */
    public function checkTransactionStatus($response, PaymentTransaction $transaction, $paymentDatas = []){
        $result = [
            "transaction" => [
                "success" => false,
                "failed" => false,
                "pending" => false,
                "message" => "",
                "stop_check_status" => true,
            ],
            "payment" => [
                "message" => __("Erreur lors de l'enregistrement local du paiement."),
                "saved" => false,
            ]
        ];

        $transactionMessage = "";
        $transactionStatus = $response['status'] ?? $response['STATUT'] ?? null;

        $transactionReference = $transaction->reference;
        $paymentMethodCode = $transaction->paymentMethod->code ?? "";

        $possiblesStatuses= (in_array($paymentMethodCode,["mtn-momo-cmr","orange-momo-cmr",'paypal-rest']))
                            ? $this->possiblesStatusesForMTNOrOrange()
                            : $this->pvitPossiblesStatuses();

         write_log("transactions/check-status/$paymentMethodCode",json_encode($response),'error',$paymentMethodCode);

        try {

            if (!blank($transactionStatus)){

                /*si le statut retourné est parmis les statuts connus par l'app*/
                if(array_key_exists($transactionStatus,$possiblesStatuses)){

                    //recupère le statut a statut pour le mode de payment envoyé
                    $success = $possiblesStatuses[$transactionStatus]['success'] ?? false;

                    // si le payment a été approuvé par le payeur
                    if ($success){
                        $successAmount = $transaction->amount ?? $response['amount'] ?? $response['MONTANT'] ?? 0;
//                    $successReference = $response['financialTransactionId'] ?? $response['txnid'] ?? $response['REF'] ;
                        if ($paymentMethodCode == "mtn-momo-cmr"){
                            $successReference = $response['financialTransactionId'];
                        }
                        elseif ($paymentMethodCode == "orange-momo-cmr"){
                            $successReference =  $response['txnid']; // ou $result['txnmode']
                        }
                        elseif ($paymentMethodCode == "paypal-rest"){
                            $successReference =  $response['id'];
                        }
                        else{ /*sinon c'est airtel-momo ou mobi-cash*/
                            $successReference =  $response['REF'] ?? $transactionReference;
                            //$pvit = new Pvit();
                           //
                            // $token = $response['TOKEN'] ?? "";
//                           $parametre =  Parametre::find($pvit->getPvitParameterId());
//                           if (!blank($parametre) and !empty($token)){
//                               $parametre->update(['valeur'=>$token]);
//                           }
                        }

                        $result['transaction']['success'] = true;
                        $result['transaction']['amount'] = $successAmount;
                        $result['transaction']['reference'] = $successReference;
                        $transactionMessage = __("Payment received");
                        $result['transaction']['stop_check_status'] = true;

                        $transaction->update(['status_code'  => PaymentTransactionStatus::APPROVED]);

                        $payer = $transaction->payer ; // Le payeur [User]

                        throw_if(!$payer,new \Exception(__("Payer not found.")));

                        //Si le payeur existe
                        if ($payer){
                            $paymentDatas['payment_method_id'] = $transaction->payment_method_id;
                            $paymentDatas['provider_reference'] = $successReference;
                            $paymentDatas['created_by_api'] = true;
                            $paymentDatas['paid_amount'] = $transaction->amount;
                            $paymentDatas['payer_number'] = $transaction->payer_number;

                            $payment = $payer->addPayment($paymentDatas); // ajoute paeiment

                            if ($payment['success']){
                                $result['payment']['saved'] = true;
                                $result['payment']['message'] = __("Paiement Enregisté localement.");
                            }else{
                                $result['payment']['message'] = implode("- <br>",$payment['errors']);
                            }
                        }
                    }
                    elseif ($transactionStatus == "PENDING"){
                        $result['transaction']['message'] = "";
                        $result['transaction']['stop_check_status'] = false;
                        $result['transaction']['pending'] = true;

                    }
                    else{ /*si le statut est different de success et pending*/
                        $errorCode = $result['reason']['code'] ?? $result['inittxnstatus'] ?? $transactionStatus;
                        //$errorMessage = $result['reason']['message'] ?? $result['confirmtxnmessage'] ?? $result['MESSAGE'] ?? $possiblesStatuses[$transactionStatus]['desc'] ??'';
                        $transactionMessage = $possiblesStatuses[$transactionStatus]['message'];

                        $transactionMessage .=  " ".__("CODE",["code" => $errorCode]);
                        $result['transaction']['failed'] = true;
                    }
                }
                else{
                    $transactionMessage = __("le statut renvoyé par le fournisseur est inconnu à l'application.");
                }
            }else{
                $transactionMessage = __("le fournisseur n'a pas répondu correctement.");
            }
            $result['transaction']['message'] = $transactionMessage;
        }catch (\Exception $exception){
            write_log("momo/ckeck-statuses/$paymentMethodCode",$exception,'error',$paymentMethodCode);
            if (request()->ajax() or request()->wantsJson()){
                return response()->json([
                    "success" => false,
                    "message" => general_error($exception),
                ],ServerStatus::APPLICATION_ERROR);

            }
        }
        return $result;

    }

}
