<?php

namespace Shm\ShmPayment\TBankPayment;



use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Request;
use Shm\Shm;
use Shm\ShmAuth\Auth;
use Shm\ShmCmd\Cmd;
use Shm\ShmDB\mDB;
use Shm\ShmPayment\TBankPayment\TBankCollections\TCustomerCards;
use Shm\ShmPayment\TBankPayment\TBankCollections\TPayments;
use Shm\ShmRPC\ShmRPC;
use Shm\ShmTypes\StructureType;
use Shm\ShmUtils\ShmTwig;
use Shm\ShmUtils\ShmUtils;

class TBankPayment
{


    const API_ENDPOINT = 'https://securepay.tinkoff.ru/v2/';

    const WEBHOOK_URL = '/webhook/tbank/payment';

    //TODO не понятно придет ли 10 рублей на счет или 9,70
    //Чуть позже проверить и если не 10 рублей то считать сколько придет
    const BANK_COMMISSION = 0.03;

    protected static  $default_http_client_options = [
        'connect_timeout' => 4,
        'timeout' => 10
    ];

    public static $terminalKey = null;
    public static $terminalPassword =  null;


    /**
     * Система налогообложения
     * osn - общая СН
     * usn_income - упрощенная СН (доходы)
     * usn_income_outcome - упрощенная СН (доходы минус расходы)
     * esn - единый сельскохозяйственный налог
     * patent - патентная СН
     */
    public static $terminalTaxation = null;

    /**
     * Налоговая ставка
     * none - без НДС
     * vat0 - НДС по ставке 0%
     * vat5 - НДС по ставке 5%
     * vat7 - НДС по ставке 7%
     * vat10 - НДС по ставке 10%
     * vat22 - НДС по ставке 22%
     * vat105 - НДС чека по расчетной ставке 5/105
     * vat107 - НДС чека по расчетной ставке 7/107
     * vat110 - НДС чека по расчетной ставке 10/110
     * vat122 - НДС чека по расчетной ставке 22/122
     */
    public static $terminalTaxRate = null;





    public $customerID = null;
    public $customerPhone = null;
    public $customerEmail = null;
    public $customerStructure = null;

    public static function setTerminalTaxation($taxation)
    {
        self::$terminalTaxation = $taxation;
    }
    public static function setTerminalTaxRate($taxRate)
    {
        self::$terminalTaxRate = $taxRate;
    }



    public static function setTerminalKey($terminalKey)
    {
        self::$terminalKey = $terminalKey;
    }
    public static function setTerminalPassword($password)
    {
        self::$terminalPassword = $password;
    }

    private function updateCustomerPhoneAndEmail(StructureType $customerStructure, $customer_id)
    {

        $phoneField = $customerStructure->findItemByType(Shm::phone());

        $emailField = $customerStructure->findItemByType(Shm::email());

        $customer = $customerStructure->findOne(
            [
                "_id" => mDB::id($customer_id),
            ]
        );

        if ($phoneField && isset($customer[$phoneField->key]) && $customer[$phoneField->key]) {
            $this->customerPhone = '+' . $customer[$phoneField->key];
        }
        if ($emailField && isset($customer[$emailField->key]) && $customer[$emailField->key]) {
            $this->customerEmail = $customer[$emailField->key];
        }
    }

    public function __construct(StructureType $customerStructure, $customer_id)
    {


        if (!self::$terminalKey || !self::$terminalPassword) {
            throw new \Exception("Terminal key or password is not set");
        }


        if (!self::$terminalTaxation) {
            throw new \Exception("Terminal taxation is not set");
        }

        if (!self::$terminalTaxRate) {
            throw new \Exception("Terminal tax rate is not set");
        }

        $this->customerStructure = $customerStructure;

        $phoneField = $customerStructure->findItemByType(Shm::phone());
        if (!$phoneField) {
            throw new \Exception("Phone field not found");
        }



        $customer = $customerStructure->findOne([
            "_id" => mDB::id($customer_id)
        ]);

        if (!$customer) {
            throw new \Exception("Customer not found");
        }
        $this->updateCustomerPhoneAndEmail($customerStructure, $customer_id);


        $this->customerID =  $customer['_id'];
    }


    public function getCustomerKey()
    {
        return md5($this->customerID . $this->customerStructure->collection . self::$terminalKey);
    }

    public static function updatePaymentState($payment_id)
    {

        $payment = TPayments::findOne([
            '_id' => mDB::id($payment_id),

        ]);
        if (!$payment) {
            throw new \Exception("Payment not found");
        }

        $paymentId = $payment['paymentId'] ?? $payment['initResult']['PaymentId'];




        $data = [
            'PaymentId' => $paymentId,
        ];
        $response = self::sendRequest('GetState', $data);

        TPayments::updateOne(
            [
                '_id' => mDB::id($payment_id)
            ],
            [
                '$set' => ['status' => $response['Status']]
            ]
        );

        // var_dump($response['Status']);
    }



    public static function CustomerCardsRpc()
    {

        return ShmRPC::makeQuery(TCustomerCards::structure())->pipeline(function () {

            Auth::authenticateOrThrow();

            return [
                [
                    '$match' => [
                        'TerminalKey' => self::$terminalKey,
                        'owner' => Auth::getAuthID(),
                        'ownerCollection' => Auth::getAuthCollection()
                    ]
                ]
            ];
        });
    }

    public function getCustomerCards()
    {
        $response = self::sendRequest('GetCardList', [
            "CustomerKey" => $this->getCustomerKey(),
            'TerminalKey' => self::$terminalKey,
            'owner' => mDB::id($this->customerID),
            'ownerCollection' => $this->customerStructure->collection,
        ]);
        return $response;
    }

    public static function updateCardsByPayment($payment_id)
    {

        $payment = TPayments::findOne([
            '_id' => mDB::id($payment_id),
        ]);

        if (! $payment) {
            return;
        }

        $CustomerKey = $payment['initRequest']['CustomerKey'] ?? null;

        if (! $CustomerKey) {
            return;
        }

        $response = self::sendRequest('GetCardList', [
            "CustomerKey" => $CustomerKey,
        ]);

        $cards = $response ?? [];

        foreach ($cards as $card) {

            TCustomerCards::updateOne(
                [
                    'CardId' => $card['CardId'],
                    'deleted_at' => ['$exists' => false],
                    "owner" => $payment->owner,
                    "ownerCollection" => $payment->ownerCollection,
                    'TerminalKey' => self::$terminalKey,

                ],
                [

                    '$set' => [
                        ...$card,
                        'TerminalKey' => self::$terminalKey,
                        "owner" => $payment->owner,
                        "ownerCollection" => $payment->ownerCollection,
                    ]

                ],
                [
                    'upsert' => true,
                ]
            );
        }
    }



    public function updateCards()
    {
        $response = self::sendRequest('GetCardList', [
            "CustomerKey" => $this->getCustomerKey(),
        ]);


        $cards = $response ?? [];

        foreach ($cards as $card) {

            TCustomerCards::updateOne(
                [
                    'CardId' => $card['CardId'],
                    'deleted_at' => ['$exists' => false],
                    "owner" => mDB::id($this->customerID),
                    "ownerCollection" => $this->customerStructure->collection,
                    'TerminalKey' => self::$terminalKey,

                ],
                [

                    '$set' => [
                        ...$card,
                        'TerminalKey' => self::$terminalKey,
                        "owner" => mDB::id($this->customerID),
                        "ownerCollection" => $this->customerStructure->collection,
                    ]

                ],
                [
                    'upsert' => true,
                ]
            );
        }
    }

    public  function GetCustomer()
    {
        $response = self::sendRequest('GetCustomer', [
            'CustomerKey' => $this->getCustomerKey(),
        ]);

        return $response;
    }

    public function Cancel($PaymentId)
    {
        $response = self::sendRequest('Cancel', [
            "PaymentId" => $PaymentId,
        ]);

        return $response;
    }

    public function Charge($PaymentId, $RebillId): bool
    {
        $response = self::sendRequest('Charge', [
            "PaymentId" => $PaymentId,
            "RebillId" => $RebillId,
        ]);

        TPayments::updateOne(
            [
                "paymentId" => $PaymentId
            ],
            [
                '$set' => [
                    'chargeResult' => $response
                ]
            ]
        );

        $Success = $response['Success'] ?? false;

        return $Success;
    }

    public  function validCard($card_id): bool
    {

        $findCard = TCustomerCards::findOne([
            '_id' => mDB::id($card_id),
            'owner' => $this->customerID
        ]);

        if (! $findCard) {
            return false;
        }



        $response = self::sendRequest('GetCardList', [
            "CustomerKey" => $this->getCustomerKey(),
        ]);



        $cards = $response ?? [];

        if (!$cards || count($cards) == 0) {
            return false;
        }

        foreach ($cards as $card) {

            if ($card['CardId'] == $findCard->CardId) {
                return true;
            }
        }

        return false;
    }

    public function addCard()
    {



        /*

        Тут есть метод добавления карты, но он не работает,
        Поэтому используем метод оплаты чтобы сохранить карту
        $data = [
            'CustomerKey' => $this->getCustomerKey(),
            'NotificationURL' => ShmUtils::url(self::WEBHOOK_URL),
            // 'CheckType' => "HOLD",
            "ResidentState" => true
        ];

        $response = self::sendRequest('AddCard', $data);

        var_dump($response);*/
    }

    /**
     * Инициализация платежа с массивом товаров
     * @param array $items
     * @param string $description
     * @param bool $autoRefunded
     * @param array $payload
     * @return string|null
     * @example
     * $items = [
     *     [
     *         "Name" =>  'Название товара'
     *         "Price" => 100, //Цена за единицу
     *         "Quantity" => 1, //Количество
     *     ]
     * ];
     */
    public function initPaymentItems($_items, $description, $autoRefunded = false, $payload = []): ?string
    {
        $orderId = uniqid() . '' . uniqid() . 'order';

        $Items = [];
        $totalAmount = 0;
        foreach ($_items as $item) {
            $totalAmount += $item['Price'] * $item['Quantity'] * 100;
            $Items[] = [
                "Name" => $item['Name'],
                "Price" => $item['Price'] * 100,
                "Quantity" => $item['Quantity'],
                "Amount" => $item['Price'] * $item['Quantity'] * 100,
                "Tax" => self::$terminalTaxRate
            ];
        }



        $data = [
            'Amount' => $totalAmount,
            'OrderId' => $orderId,
            'Description' => $description,
            'Recurrent' => 'Y',
            'CustomerKey' => $this->getCustomerKey(),
            'NotificationURL' => ShmUtils::url(self::WEBHOOK_URL),
        ];

        if ($this->customerPhone) {
            $data['DATA']['Phone'] = $this->customerPhone;
        }
        if ($this->customerEmail) {
            $data['DATA']['Email'] = $this->customerEmail;
        }

        $data['SuccessURL'] = ShmUtils::url('/payment/status/success');
        $data['FailURL'] = ShmUtils::url('/payment/status/error');


        $data['Receipt'] = [
            //   'Phone' => $this->customerPhone,
            "Taxation" => self::$terminalTaxation,
            "Items" => $Items,
        ];
        if ($this->customerPhone) {
            $data['Receipt']['Phone'] = $this->customerPhone;
        }
        if ($this->customerEmail) {
            $data['Receipt']['Email'] = $this->customerEmail;
        }


        $payment = TPayments::insertOne([
            "amount" => $totalAmount / 100,
            "orderId" => $orderId,
            "description" => $description,
            "phone" => $this->customerPhone,
            "email" => $this->customerEmail,
            "owner" => mDB::id($this->customerID),
            "ownerCollection" => $this->customerStructure->collection,
            'initRequest' => $data,
            'terminalKey' => self::$terminalKey,
            'autoRefunded' => $autoRefunded,
            'notifications' => [],
            'payload' => $payload,
        ]);


        $response = self::sendRequest('Init', $data);

        TPayments::updateOne(
            [
                '_id' => $payment->getInsertedId()
            ],
            [
                '$set' => [
                    'status' => $response['Status'] ?? "INIT_ERROR",
                    'paymentId' => $response['PaymentId'] ?? null,
                    'initResult' => $response
                ]
            ]
        );

        return $response['PaymentId'] ?? null;
    }

    public  function initPayment($sum, $description, $autoRefunded = false, $payload = []): ?string
    {

        $orderId = uniqid() . '' . uniqid() . 'order';

        $data = [
            'Amount' => $sum * 100,
            'OrderId' => $orderId,
            'Description' => $description,
            'Recurrent' => 'Y',
            'CustomerKey' => $this->getCustomerKey(),

            'NotificationURL' => ShmUtils::url(self::WEBHOOK_URL),
        ];

        if ($this->customerPhone) {
            $data['DATA']['Phone'] = $this->customerPhone;
        }
        if ($this->customerEmail) {
            $data['DATA']['Email'] = $this->customerEmail;
        }

        $data['SuccessURL'] = ShmUtils::url('/payment/status/success');
        $data['FailURL'] = ShmUtils::url('/payment/status/error');


        $data['Receipt'] = [
            //   'Phone' => $this->customerPhone,
            "Taxation" => self::$terminalTaxation,
            "Items" => [
                [
                    "Name" => $description,
                    "Price" => $sum * 100,
                    "Quantity" => 1,
                    "Amount" => $sum * 100,
                    "Tax" => self::$terminalTaxRate
                ]
            ],
        ];
        if ($this->customerPhone) {
            $data['Receipt']['Phone'] = $this->customerPhone;
        }
        if ($this->customerEmail) {
            $data['Receipt']['Email'] = $this->customerEmail;
        }



        $payment = TPayments::insertOne([
            "amount" => $sum,
            "orderId" => $orderId,
            "description" => $description,
            "phone" => $this->customerPhone,
            "email" => $this->customerEmail,
            "owner" => mDB::id($this->customerID),
            "ownerCollection" => $this->customerStructure->collection,
            'initRequest' => $data,
            'terminalKey' => self::$terminalKey,
            'autoRefunded' => $autoRefunded,
            'notifications' => [],
            'payload' => $payload,
        ]);


        $response = self::sendRequest('Init', $data);

        TPayments::updateOne(
            [
                '_id' => $payment->getInsertedId()
            ],
            [
                '$set' => [
                    'status' => $response['Status'] ?? "INIT_ERROR",
                    'paymentId' => $response['PaymentId'] ?? null,
                    'initResult' => $response
                ]
            ]
        );

        return $response['PaymentURL'] ?? null;
    }

    public function removeCard($CardId)
    {

        $response = self::sendRequest('RemoveCard', [
            "CardId" => $CardId,
            "CustomerKey" => $this->getCustomerKey(),
        ]);

        return $response;
    }


    protected static function generateToken(array $params)
    {
        foreach (['SHOPS', 'RECEIPT', 'DATA'] as $ignore_key) {
            if (isset($params[$ignore_key])) {
                unset($params[$ignore_key]);
            }
        }

        $params['Password'] = self::$terminalPassword;
        ksort($params);

        $token = '';
        foreach ($params as $param_value) {
            if (is_scalar($param_value)) {
                $token .= $param_value;
            }
        }

        return hash('sha256', $token);
    }

    public  static function sendRequest($method, $data)
    {


        $data['TerminalKey'] = self::$terminalKey;


        $data['Token'] = self::generateToken($data);
        $url = self::API_ENDPOINT . $method;




        $headers = [
            'Content-Type' => 'application/json',
        ];


        $client = new Client(self::$default_http_client_options);


        $request = new Request('POST', $url, $headers, json_encode($data));
        $response = $client->sendAsync($request)->wait();


        $body = json_decode($response->getBody(), true);



        return $body;
    }


    public static function routesInit()
    {


        Cmd::command("tbankWorker", function () {


            //Проверка операций которые надо отменить 
            $operation = TPayments::findOne([
                'status' => 'CONFIRMED',
                'autoRefunded' => true,
                'cancelResult' => ['$exists' => false],
            ]);




            if ($operation) {


                $paymentId = $operation['paymentId'] ?? null;



                if ($paymentId) {
                    $response = self::sendRequest('Cancel', [
                        "PaymentId" => $paymentId,
                    ]);

                    $status = $response['Status'] ?? null;

                    $set = [
                        'cancelResult' => $response,
                    ];
                    if ($status) {
                        $set['status'] = $status;
                    }
                    TPayments::updateOne([
                        '_id' => $operation['_id']
                    ], [
                        '$set' => $set
                    ]);
                }
            }
        })->everyMinute();


        $f3 = \Base::instance();
        $f3->route('POST|GET ' . self::WEBHOOK_URL, function ($f3) {

            $data = ShmUtils::allRequest();

            $OrderId = $data['OrderId'] ?? null;

            if ($OrderId) {

                $payment = TPayments::findOne([
                    'orderId' => $OrderId
                ]);

                if ($payment) {

                    /**Requirements: <= 1 characters, [Y]
                     * Признак родительского CC-платежа. 
                     * Обязателен для проведения операции с сохранением реквизитов карты покупателя.
                     * Если передается и установлен в Y, при платеже будут сохранены реквизиты карты 
                     * покупателя. В этом случае после оплаты в уведомлении на AUTHORIZED 
                     * будет передан параметр RebillId для использования 
                     * в методе Провести платеж по сохраненным реквизитам. Для привязки и одновременной оплаты по CБП передавайте Y.*/


                    $dataStatus = $data['Status'] ?? null;
                    $dataRebillId = $data['RebillId'] ?? null;

                    if ($dataStatus == 'AUTHORIZED' && $dataRebillId) {

                        TBankPayment::updateCardsByPayment($payment['_id']);
                    }


                    TPayments::updateOne([
                        '_id' => $payment['_id']
                    ], [
                        '$push' => [
                            'notifications' => $data
                        ]
                    ]);

                    TBankPayment::updatePaymentState($payment['_id']);
                }
            }
        });

        $f3->route('GET /payment/status/success', function ($f3) {
            ShmTwig::html('@shm/payment/payment_success.twig');
        });
        $f3->route('GET /payment/status/error', function ($f3) {
            ShmTwig::html('@shm/payment/payment_error.twig');
        });
    }
}