<?php

namespace App\Services;

use App\Models\BillingTransaction;
use App\Models\Plan;
use App\Models\Subscription;
use App\Models\User;
use App\Support\CurrentBusiness;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use RuntimeException;

class PaystackService
{
    public function initialize(Plan $plan, User $user): string
    {
        throw_if($plan->price_kobo < 1, RuntimeException::class, 'This plan cannot be purchased online.');
        $reference = 'AJO-'.Str::upper(Str::random(24));
        $transaction = BillingTransaction::create(['plan_id' => $plan->id, 'initiated_by' => $user->id, 'reference' => $reference, 'amount_kobo' => $plan->price_kobo, 'currency' => $user->business->currency]);

        $response = $this->client()->post('/transaction/initialize', [
            'email' => $user->email,
            'amount' => (string) $transaction->amount_kobo,
            'currency' => $transaction->currency,
            'reference' => $reference,
            'callback_url' => route('billing.callback'),
            'metadata' => ['business_id' => $user->business_id, 'plan_id' => $plan->id],
        ])->throw()->json();

        throw_unless(data_get($response, 'status') && data_get($response, 'data.authorization_url'), RuntimeException::class, 'Paystack could not initialize this payment.');
        $transaction->update(['provider_payload' => $response]);

        return $response['data']['authorization_url'];
    }

    public function verifyAndFulfill(string $reference): BillingTransaction
    {
        $payload = $this->client()->get('/transaction/verify/'.rawurlencode($reference))->throw()->json('data');

        return $this->fulfill($reference, $payload ?? []);
    }

    public function fulfill(string $reference, array $payload): BillingTransaction
    {
        return DB::transaction(function () use ($reference, $payload): BillingTransaction {
            $transaction = BillingTransaction::withoutGlobalScope('business')->lockForUpdate()->where('reference', $reference)->firstOrFail();
            if ($transaction->status === 'success') {
                return $transaction;
            }

            $valid = ($payload['status'] ?? null) === 'success'
                && (int) ($payload['amount'] ?? 0) === $transaction->amount_kobo
                && ($payload['currency'] ?? null) === $transaction->currency
                && ($payload['reference'] ?? null) === $transaction->reference;
            throw_unless($valid, RuntimeException::class, 'Payment verification failed.');

            $transaction->update(['status' => 'success', 'paid_at' => now(), 'provider_payload' => $payload]);
            $transaction->load('business');
            app(CurrentBusiness::class)->set($transaction->business);
            Subscription::withoutGlobalScope('business')->updateOrCreate(
                ['business_id' => $transaction->business_id, 'provider_reference' => $transaction->reference],
                ['plan_id' => $transaction->plan_id, 'status' => 'active', 'provider' => 'paystack', 'starts_at' => now(), 'ends_at' => now()->addMonth(), 'metadata' => ['transaction_id' => $payload['id'] ?? null]]
            );

            return $transaction;
        });
    }

    public function validSignature(string $body, ?string $signature): bool
    {
        return is_string($signature) && hash_equals(hash_hmac('sha512', $body, (string) config('services.paystack.secret_key')), $signature);
    }

    private function client(): PendingRequest
    {
        throw_if(blank(config('services.paystack.secret_key')), RuntimeException::class, 'Paystack is not configured.');

        return Http::baseUrl('https://api.paystack.co')->withToken(config('services.paystack.secret_key'))->acceptJson()->timeout(20)->retry(2, 300);
    }
}
