<?php

namespace Yajny\EventsYj\Library\Tpay;

use App\Exceptions\FailedToInquirePayMobTransactionsException;
use App\Exceptions\GeneratePayMobAccessTokenException;
use App\Models\PayoutGateway;
use Illuminate\Support\Facades\Log;
use Yajny\EventsYj\Events\PayoutUpdated;
use Yajny\Member\Entities\Payouts\PayoutGatewaysUtil;
use Yajny\Member\Entities\Payouts\PayoutStatusUtil;
use Yajny\Member\Models\Member;
use Yajny\Member\Models\UserPayoutMethods;
use Yajny\Member\Models\UserPayouts;

class TpayPayout
{
    /** @var PayoutGateway $payoutGateway */
    private $payoutGateway = null;

    protected $payout = null;

    protected $method = null;

    protected $payoutMethod = null;

    public function __construct(UserPayouts $payout = null, UserPayoutMethods $method = null)
    {
        $this->payoutGateway = PayoutGateway::findOrFail(PayoutGatewaysUtil::T_PAY);

        if ($payout) {
            $this->payout = $payout;
            $this->method = $method;
        }
    }

    private function getAccessToken()
    {
        if ($this->isAccessTokenValid()) {
            return $this->payoutGateway->access_token;
        }
        if ($this->generateToken()) {
            return $this->payoutGateway->access_token;
        }
        throw new GenerateTpayAccessTokenException();
    }

    public function process()
    {
        $headers = [
            'Content-Type: application/json',
            "Authorization: Bearer {$this->getAccessToken()}"
        ];

        $ch = curl_init();

        $options = [
            CURLOPT_URL => "https://{$this->TpayBaseURL}disburse/",
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_CUSTOMREQUEST => "POST",
            CURLOPT_POSTFIELDS => json_encode($this->getPayMobDisburseBody()),
            CURLOPT_HTTPHEADER => $headers
        ];

        curl_setopt_array($ch, $options);

        $response = curl_exec($ch);

        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE) == 200;

        curl_close($ch);

        $this->handlePayMobDisburseResult($status, $response);

        return $status;
    }

    private function handlePayMobDisburseResult($status, $response)
    {
        $this->payout->api_response = $response;
        if ($status) {
            $response = json_decode($response);
            if (isset($response->transaction_id)) {
                $this->payout->payment_ref_num = $response->transaction_id;
                $this->updateStatus($response);
            } else {
                Log::error('Unexpected response', ['response' => $response, 'payout' => $this->payout->toArray(), 'status' => $status]);
                $this->payout->status = PayoutStatusUtil::FAILED;
            }
        } else {
            Log::error('Unexpected response', ['response' => $response, 'payout' => $this->payout->toArray(), 'status' => $status]);
            $this->payout->status = PayoutStatusUtil::FAILED;
        }
        $this->payout->save();
        if ($this->payout->status != PayoutStatusUtil::FAILED)
            event(new PayoutUpdated(Member::find($this->payout->user_id), $this->payout));
    }

    private function isAccessTokenValid()
    {
        if (isset($this->payoutGateway->access_token)) {
            return strtotime('now') < strtotime($this->payoutGateway->expires_at);
        }
        return false;
    }

    private function refreshToken()
    {
        if (isset($this->payoutGateway->extra_data['refresh_token'])) {
            $body = [
                'grant_type' => 'refresh_token',
                'refresh_token' => $this->payoutGateway->extra_data['refresh_token']
            ];
            return $this->updateTokenDataFromCurl($body);
        }
        return false;
    }

    private function generateToken()
    {
        $body = [
            'grant_type' => 'password',
            'username' => $this->payoutGateway->extra_data['username'],
            'password' => $this->payoutGateway->extra_data['password']
        ];
        return $this->updateTokenDataFromCurl($body);
    }

    private function updateTokenDataFromCurl($requestBody)
    {
        $response = '';
        if ($this->tokenCurlCaller('o/token/', $requestBody, $response)) {
            $response = json_decode($response);
            if (isset($response->refresh_token)) {
                $extraData = $this->payoutGateway->extra_data;
                $extraData['refresh_token'] = $response->refresh_token;
                $this->payoutGateway->expires_at = date('Y-m-d H:i:s', strtotime("+ {$response->expires_in} seconds"));
                $this->payoutGateway->access_token = $response->access_token;
                $this->payoutGateway->extra_data = $extraData;
                $this->payoutGateway->save();
                return true;
            }
        }
        return false;
    }

    private function tokenCurlCaller($endPoint, $body, &$response)
    {
        $username = $this->payoutGateway->extra_data['client_id'];
        $password = $this->payoutGateway->extra_data['client_secret'];

        $headers = [];

        $ch = curl_init();

        $options = [
            CURLOPT_URL => "https://{$username}:{$password}@{$this->TpayBaseURL}{$endPoint}",
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_CUSTOMREQUEST => "POST",
            CURLOPT_POSTFIELDS => $body,
            CURLOPT_HTTPHEADER => $headers
        ];

        curl_setopt_array($ch, $options);

        $response = curl_exec($ch);

        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE) == 200;

        curl_close($ch);
        return $status;
    }

    public function inquire($transactionIDs, $isBankTransactions = false)
    {
        $headers = [
            'Content-Type: application/json',
            "Authorization: Bearer {$this->getAccessToken()}"
        ];

        $body = [
            'transactions_ids_list' => $transactionIDs
        ];

        if ($isBankTransactions) {
            $body['bank_transactions'] = true;
        }

        $ch = curl_init();

        $options = [
            CURLOPT_URL => "https://{$this->TpayBaseURL}transaction/inquire/",
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_CUSTOMREQUEST => "GET",
            CURLOPT_POSTFIELDS => json_encode($body),
            CURLOPT_HTTPHEADER => $headers
        ];

        curl_setopt_array($ch, $options);

        $response = curl_exec($ch);

        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE) == 200;

        curl_close($ch);

        $this->handlePayMobInquireResult($status, $response);

        return $status;
    }

    private function handlePayMobInquireResult($status, $response)
    {
        if ($status) {
            $response = json_decode($response);
            foreach ($response->results as $transaction) {
                $this->payout = UserPayouts::where('payment_ref_num', $transaction->transaction_id)->first();
                if ($this->payout) {
                    $isStatusUpdated = $this->updateStatus($transaction);
                    if ($isStatusUpdated) {
                        $this->payout->save();
                        event(new PayoutUpdated(Member::find($this->payout->user_id), $this->payout));
                    }
                } else {
                    Log::error('Failed to find payout', compact('transaction', 'response'));
                }
            }
        } else {
            throw new FailedToInquirePayMobTransactionsException();
        }
    }

    abstract function updateStatus($transaction);

    public function getBalance()
    {
        $headers = [
            'Content-Type: application/json',
            "Authorization: Bearer {$this->getAccessToken()}"
        ];

        $ch = curl_init();

        $options = [
            CURLOPT_URL => "https://{$this->TpayBaseURL}budget/inquire/",
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_CUSTOMREQUEST => "GET",
            CURLOPT_HTTPHEADER => $headers
        ];

        curl_setopt_array($ch, $options);

        $response = curl_exec($ch);

        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE) == 200;

        curl_close($ch);

        if ($status) {
            return json_decode($response, true)['current_budget'] ?? null;
        }
        Log::error('Unexpected behavior', compact('status', 'response'));
        return null;
    }
}
