<?php

namespace App\Services\Payment\Methods;

use App\Exceptions\PaymentProcessingException;
use App\Models\PaymentMethod;
use App\Services\Payment\Payable;
use App\Services\Payment\Traits\PaymentServiceHelper;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;

class PaystackService extends AbstractPayment
{
    use PaymentServiceHelper;

    private $base_url = 'https://api.paystack.co';

    private Client $client;

    /**
     * Transaction Verification Successful
     */
    const VS = 'Verification successful';

    /**
     *  Invalid Transaction reference
     */
    const ITF = 'Invalid transaction reference';

    public function pay(Payable $payable, $rest = [])
    {
        $this->setUp($payable->getMethod());

        $payment_url = $this->makePayment($payable);

        $payment = $this->createPayment($payable);

        return [
            'payment' => $payment,
            'response' => [
                'payment_url' => $payment_url,
            ],
        ];
    }

    public function handleWebhook(Request $request)
    {
        $this->setUp($this->getPaymentMethod('paystack'));

        $paymentMethod = $this->getPaymentMethod('paystack');

        $meta = $paymentMethod->meta;

        $paystackSignature = $request->header('x-paystack-signature');

        $hash = hash_hmac('sha512', $request->getContent(), $meta['secret_key']);

        if (! hash_equals($hash, $paystackSignature)) {
            throw new PaymentProcessingException('Invalid request');
        }

        $content = $request->getContent();

        $content = json_decode($content, true);

        $event = $content['event'];

        $pid = $content['data']['reference'];

        $payment = $this->getPaymentByPid($pid);

        if (! $payment) {
            throw new PaymentProcessingException(__('Payment not found'), code: 1);
        }

        return match ($event) {
            'charge.success' => [
                'payment' => $payment,
                'status' => 'success',
            ],
            'charge.failed' => [
                'payment' => $payment,
                'status' => 'failed',
            ],
            default => throw new PaymentProcessingException('Invalid request', code: 1),
        };

    }

    public function handleSuccess(Request $request)
    {
        if (! $request->filled('trxref')) {
            throw new PaymentProcessingException(__('Transaction reference not found'));
        }

        $pid = $request->trxref;

        $this->setUp($this->getPaymentMethod('paystack'));

        if (! $this->paymentExists($pid)) {
            throw new PaymentProcessingException(__('Payment is not successful'));
        }

        return $pid;
    }

    public function handleFailed(Request $request)
    {
        throw new PaymentProcessingException('Payment failed');
    }

    public function checkOrderStatus($pid)
    {
        $this->setUp($this->getPaymentMethod('paystack'));

        if (! $pid) {
            throw new PaymentProcessingException(__('Invalid request'));
        }

        $status = $this->paymentExists($pid, true);

        if ($status === 'success') {
            return 'success';
        }

        if ($status === 'cancel') {
            return 'cancel';
        }

        return 'pending';
    }

    private function makePayment(Payable $payable)
    {
        $user = auth('client')->user();
        $reference = $payable->getPid();
        // check if the reference id is already in use
        if ($this->paymentExists($reference)) {
            // update old payment
            $this->cancelPayment($reference);
            // create new payment with new reference
            $reference = $payable->generatePid();
            $this->createPayment($payable, pid: $reference);
        }

        $data = array_filter([
            'amount' => intval($payable->getConvertedAmount() * 100),
            'reference' => $reference,
            'email' => $user->email,
            'first_name' => $user->first_name,
            'last_name' => $user->last_name,
            'callback_url' => $this->getReturnUrl($payable->getMethod(), pid: $reference),
            'currency' => $payable->getCurrency(),
        ]);

        $response = $this->client->post('/transaction/initialize', [
            'body' => json_encode($data),
        ]);

        $response = $this->guzzleResponseFormat($response);

        if (isset($response['data']['authorization_url'])) {
            return $response['data']['authorization_url'];
        }

        throw new PaymentProcessingException('Payment failed');
    }

    private function paymentExists($reference, $withStatus = false)
    {
        try {
            $response = $this->client->get('/transaction/verify/'.$reference);

            $result = $this->guzzleResponseFormat($response);

            if ($withStatus) {
                return $result['data']['status'];
            }

            $validate = false;

            switch ($result['message']) {
                case self::VS:
                    $validate = true;
                    break;
                case self::ITF:
                    $validate = false;
                    break;
                default:
                    $validate = false;
                    break;
            }

            return $validate;
        } catch (\Exception $th) {
            Log::info($th->getMessage());

            return false;
        }

    }

    private function setUp(PaymentMethod $paymentMethod)
    {
        $config = $paymentMethod->meta;
        if (! isset($config['secret_key']) || ! isset($config['public_key'])) {
            throw new PaymentProcessingException(__('Something want wrong!'));
        }

        $authBearer = 'Bearer '.$config['secret_key'];

        $this->client = new Client(
            [
                'base_uri' => $this->base_url,
                'headers' => [
                    'Authorization' => $authBearer,
                    'Content-Type' => 'application/json',
                    'Accept' => 'application/json',
                ],
            ]
        );
    }
}
