<?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 Flutterwave\Config\PackageConfig;
use Flutterwave\Flutterwave;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;

class FlutterwaveService extends AbstractPayment
{
    use PaymentServiceHelper;

    private $base_url = 'https://api.flutterwave.com/v3';

    const STATUS_SUCCESSFUL = 'successful';

    const STATUS_FAILED = 'failed';

    private $secret_key = null;

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

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

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

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

    private function makePayment(Payable $payable)
    {
        $user = auth('client')->user();
        $this->setUp($payable->getMethod());
        $pid = $payable->getPid();

        $transaction = $this->getTransaction($pid);
        if (isset($transaction['id'])) {
            // update old payment
            $this->cancelPayment($pid);
            // create new payment with new reference
            $pid = $payable->generatePid();
            $this->createPayment($payable, pid: $pid);
        }

        $data = [
            'amount' => $payable->getPayableAmount(),
            'currency' => $payable->getCurrency(),
            'tx_ref' => $pid,
            'redirect_url' => $this->getReturnUrl($payable->getMethod(), $pid),
            'customer' => [
                'email' => $user->email,
                'phone' => $user->phone,
                'name' => $user->full_name,
            ],
            'customizations' => [
                'title' => config('app.name'),
                'logo' => 'https://png.pngtree.com/element_pic/16/11/03/dda587d35b48fd01947cf38931323161.jpg',
            ],
        ];

        $response = Http::withHeaders([
            'Content-Type' => 'application/json',
            'Authorization' => 'Bearer '.$this->secret_key,
        ])->post($this->base_url.'/payments', $data);

        if ($response->failed()) {
            throw new PaymentProcessingException('Payment initialization failed');
        }

        if ($response->json()['status'] !== 'success') {
            throw new PaymentProcessingException($response->json()['message']);
        }

        return $response->json()['data'];
    }

    public function handleWebhook(Request $request)
    {
        $secretHash = $this->getConfig('flutterwave', 'secret_hash');
        $signature = $request->header('verif-hash');

        if (! $signature || ($signature !== $secretHash)) {
            throw new PaymentProcessingException('Invalid signature');
        }

        $event = $request->all();

        $transaction = $event['data'];

        $pid = $transaction['tx_ref'];

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

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

        $payment->gateway_id = $transaction['id'];
        $payment->save();

        if ($event['event'] === 'charge.completed') {
            return $this->handleChargeCompleteEvent($transaction);
        }

        throw new PaymentProcessingException('Unknown event', code: 1);
    }

    public function handleSuccess(Request $request)
    {
        $this->setUp($this->getPaymentMethod('flutterwave'));

        if (! $request->filled('tx_ref', 'transaction_id')) {
            throw new PaymentProcessingException(__('Payment is not successful'));
        }

        $pid = $request->tx_ref;

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

        if (! $payment) {
            throw new PaymentProcessingException('Payment not found');
        }

        $transaction = $this->getTransaction($pid);

        if (! $this->validatedTransaction($transaction)) {
            throw new PaymentProcessingException(message: __('Payment is not successful'));
        }

        $payment->gateway_id = $transaction['id'];
        $payment->save();

        return $pid;
    }

    public function handleFailed(Request $request)
    {
        $this->setUp($this->getPaymentMethod('flutterwave'));

        if (! $request->filled('tx_ref', 'transaction_id')) {
            throw new PaymentProcessingException(__('Payment is not successful'));
        }

        $pid = $request->tx_ref;

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

        if (! $payment) {
            throw new PaymentProcessingException('Payment not found');
        }

        $transaction = $this->getTransaction($pid);

        $payment->gateway_id = $transaction['id'];
        $payment->save();

        return $pid;
    }

    private function setUp(PaymentMethod $paymentMethod)
    {
        $config = $paymentMethod->meta;

        $fwConfig = PackageConfig::setUp($config['secret_key'], $config['public_key'], $config['encryption_key'], $paymentMethod->is_test ? 'staging' : 'production');

        $this->secret_key = $config['secret_key'];

        Flutterwave::bootstrap($fwConfig);
    }

    private function getTransaction($pid)
    {
        $response = Http::withHeaders([
            'Content-Type' => 'application/json',
            'Authorization' => 'Bearer '.$this->secret_key,
        ])->get("https://api.flutterwave.com/v3/transactions/verify_by_reference?tx_ref=$pid");

        $data = $response->json();

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

        return $data;
    }

    private function validatedTransaction($transaction)
    {
        return $transaction && isset($transaction['status']) && $transaction['status'] === $this::STATUS_SUCCESSFUL;
    }

    private function handleChargeCompleteEvent($transaction)
    {
        $pid = $transaction['tx_ref'];
        $payment = $this->getPaymentByPid($pid);

        return match ($transaction['status']) {
            self::STATUS_SUCCESSFUL => [
                'status' => 'success',
                'payment' => $payment,
            ],
            self::STATUS_FAILED => [
                'status' => 'failed',
                'payment' => $payment,
            ],
            default => $this->handleUnknownEvent(),
        };
    }

    private function handleUnknownEvent()
    {
        throw new PaymentProcessingException('Unknown event', code: 1);
    }
}
