<?php

namespace App\Services\Payment\Methods;

use App\Exceptions\PaymentProcessingException;
use App\Models\Payment;
use App\Models\PaymentMethod;
use App\Services\Payment\Payable;
use App\Services\Payment\Traits\PaymentServiceHelper;
use Illuminate\Http\Request;
use Stripe\StripeClient;
use Stripe\Webhook;

class StripeService extends AbstractPayment
{
    use PaymentServiceHelper;

    private $stripe = null;

    private $public_key = null;

    private $webhook_secret = null;

    const STRIPE_PAID = 'paid';

    const STRIPE_UNPAID = 'unpaid';

    const STRIPE_CANCEL = 'cancel';

    const STRIPE_CHARGE_SUCCEED = 'succeeded';

    public function pay(Payable $payable, $rest = [])
    {

        $this->setStripe($payable->getMethod());

        $transaction = $this->createPaymentIntent($payable, $rest);
        $payment = $this->createPayment($payable, $transaction['intent_id']);

        return [
            'payment' => $payment,
            'response' => [
                ...$transaction,
                'gateway' => $payable->getMethod()->slug,
                'pid' => $payable->getPid(),
            ],
        ];
    }

    private function createPaymentIntent(Payable $payable, $rest = [])
    {
        $stripe = $this->stripe;

        $stripe_user_id = $this->getStripeCustomerId();

        $paymentIntent = $stripe->paymentIntents->create([
            'amount' => $this->formatAmountForStripe($payable->getAmount(), $payable->getCurrency()),
            'currency' => $payable->getCurrency(),
            'customer' => $stripe_user_id,
            'automatic_payment_methods' => ['enabled' => true],
            'metadata' => [
                'pid' => $payable->getPid(),
                ...$rest,
            ],
        ]);

        return [
            'intent_id' => $paymentIntent->id,
            'client_secret' => $paymentIntent->client_secret,
            'public_key' => $this->public_key,
            'return_url' => route('payment.return', ['pid' => $payable->getPid(), 'gateway' => $payable->getMethod()->slug]),
        ];
    }

    public function getStripeCustomerId()
    {
        $user = auth('client')->user();

        $stripe_user_id = $user->payment_user_id;

        if ($stripe_user_id) {
            $retrieve = $this->stripe->customers->retrieve($stripe_user_id);

            if (! $retrieve) {
                $stripe_user_id = null;
            }
        }

        if (! $stripe_user_id) {
            $stripe_user = $this->stripe->customers->create([
                'email' => $user->email,
                'name' => $user->full_name,
                'address' => [
                    'city' => $user->city,
                    'country' => $user->country,
                    'postal_code' => $user->zip_code,
                ],
            ]);

            $stripe_user_id = $stripe_user->id;

            $user->payment_user_id = $stripe_user_id;

            $user->save();

            return $stripe_user_id;
        }

        return $stripe_user_id;
    }

    public function handleSuccess(Request $request)
    {
        $this->setStripe($this->getPaymentMethod('stripe'));

        $payment = $this->getPaymentByPid($request->input('pid'));

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

        return $payment->payment_identifier;
    }

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

    public function handleWebhook(Request $request)
    {
        $this->setStripe($this->getPaymentMethod('stripe'));

        $event = $this->verifyWebhookSignature($request);

        $intentObject = $event?->data?->object;

        $meta = $intentObject?->meta;

        $payment = $this->getPaymentBySessionId($meta->pid);

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

        $response = match ($event->type) {
            'payment_intent.succeeded' => $this->handleChargeSuccessEvent($event),
            'charge.canceled' => [
                'payment' => $payment,
                'status' => self::CANCELLED,
            ],
            'charge.payment_failed' => [
                'payment' => $payment,
                'status' => self::FAILED,
            ],
            default => $this->handleUnknownEvent($event),
        };

        return $response;
    }

    private function formatAmountForStripe($amount, $currency)
    {
        $noMinorUnitCurrencies = ['BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'UGX', 'VND', 'VUV', 'XAF', 'XOF', 'XPF'];

        if (! in_array(strtoupper($currency), $noMinorUnitCurrencies)) {
            $amount *= 100;
        }

        return intval($amount);
    }

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

        if (! $this->stripe) {
            $this->stripe = new StripeClient($config['secret_key']);
        }

        if (! $this->public_key) {
            $this->public_key = $config['public_key'];
        }

        if (! $this->webhook_secret) {
            $this->webhook_secret = $config['webhook_secret'];
        }
    }

    private function verifyWebhookSignature($request)
    {
        $payload = @file_get_contents('php://input');
        $sigHeader = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? null;
        $endpointSecret = $this->webhook_secret;

        return Webhook::constructEvent($payload, $sigHeader, $endpointSecret);
    }

    private function checkPaymentStatus(Payment $payment)
    {
        $payment_intent = $this->stripe->paymentIntents->retrieve($payment->gateway_id);

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

        // check is payment is succeeded
        if ($payment_intent->status == self::STRIPE_CHARGE_SUCCEED) {
            return $payment;
        }

        return null;
    }

    protected function handleChargeFailedEvent($event)
    {
        throw new PaymentProcessingException('Charge failed event captured', code: 1);
    }

    protected function handleChargeSuccessEvent($event)
    {
        throw new PaymentProcessingException('Charge succeeded event captured', code: 1);
    }

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