<?php

namespace App\Jobs;

use App\Models\IntegrationEvent;
use App\Models\Plan;
use App\Models\Subscription;
use App\Models\SubscriptionInvoice;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class ProcessPaystackWebhook implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 5;

    public $backoff = [10, 30, 60, 120, 300];

    public function __construct(public IntegrationEvent $event)
    {
        $this->onQueue('integrations');
    }

    public function handle(): void
    {
        $event = $this->event->fresh();

        if (!$event || $event->status === 'processed') {
            return;
        }

        $event->update(['status' => 'processing']);

        DB::transaction(function () use ($event) {
            $payload = $event->payload ?? [];
            $data = Arr::get($payload, 'data', []);
            $eventName = Arr::get($payload, 'event');

            switch ($eventName) {
                case 'subscription.create':
                    $this->handleSubscriptionCreate($data);
                    break;
                case 'subscription.disable':
                case 'subscription.terminate':
                    $this->handleSubscriptionCancel($data);
                    break;
                case 'invoice.create':
                    $this->handleInvoiceCreate($data);
                    break;
                case 'invoice.payment_failed':
                    $this->handleInvoicePaymentFailed($data);
                    break;
                case 'invoice.payment_success':
                case 'invoice.payment_succeeded':
                    $this->handleInvoicePaymentSuccess($data);
                    break;
                default:
                    Log::info('Unhandled Paystack event', ['event' => $eventName]);
            }

            $event->update([
                'status' => 'processed',
                'processed_at' => now(),
            ]);
        });
    }

    public function failed(\Throwable $exception): void
    {
        if ($this->event->exists) {
            $this->event->update([
                'status' => 'failed',
                'processed_at' => now(),
            ]);
        }

        Log::error('Paystack webhook processing failed', [
            'event_id' => $this->event->id ?? null,
            'message' => $exception->getMessage(),
        ]);
    }

    private function handleSubscriptionCreate(array $data): void
    {
        $subscriptionCode = Arr::get($data, 'subscription_code') ?? Arr::get($data, 'subscription_code', Arr::get($data, 'subscription.subscription_code'));

        if (!$subscriptionCode) {
            return;
        }

        $subscription = Subscription::query()
            ->where('provider', 'paystack')
            ->where('provider_subscription_id', $subscriptionCode)
            ->first();

        if (!$subscription) {
            return;
        }

        $planCode = Arr::get($data, 'plan.plan_code') ?? Arr::get($data, 'plan_code');
        if ($planCode) {
            $plan = Plan::where('code', $planCode)->first();
            if ($plan) {
                $subscription->plan()->associate($plan);
            }
        }

        $subscription->update([
            'status' => Arr::get($data, 'status', 'active'),
            'renews_at' => $this->parseDate(Arr::get($data, 'next_payment_date')),
            'meta' => array_merge($subscription->meta ?? [], ['paystack' => $data]),
        ]);
    }

    private function handleSubscriptionCancel(array $data): void
    {
        $subscriptionCode = Arr::get($data, 'subscription_code') ?? Arr::get($data, 'subscription.subscription_code');

        if (!$subscriptionCode) {
            return;
        }

        Subscription::query()
            ->where('provider', 'paystack')
            ->where('provider_subscription_id', $subscriptionCode)
            ->update([
                'status' => 'canceled',
                'ends_at' => now(),
            ]);
    }

    private function handleInvoiceCreate(array $data): void
    {
        $subscription = $this->findSubscriptionFromInvoice($data);
        if (!$subscription) {
            return;
        }

        $invoiceCode = Arr::get($data, 'invoice_code') ?? Arr::get($data, 'code');

        if (!$invoiceCode) {
            return;
        }

        SubscriptionInvoice::updateOrCreate(
            ['provider_invoice_id' => $invoiceCode],
            [
                'subscription_id' => $subscription->id,
                'amount_cents' => (int) Arr::get($data, 'amount', 0),
                'currency' => Arr::get($data, 'currency', 'NGN'),
                'status' => Arr::get($data, 'status', 'pending'),
                'meta' => $data,
            ]
        );
    }

    private function handleInvoicePaymentSuccess(array $data): void
    {
        $this->updateInvoiceStatus($data, 'paid', Arr::get($data, 'paid_at'));
    }

    private function handleInvoicePaymentFailed(array $data): void
    {
        $this->updateInvoiceStatus($data, 'failed');
    }

    private function updateInvoiceStatus(array $data, string $status, ?string $paidAt = null): void
    {
        $subscription = $this->findSubscriptionFromInvoice($data);
        if (!$subscription) {
            return;
        }

        $invoiceCode = Arr::get($data, 'invoice_code') ?? Arr::get($data, 'code');
        if (!$invoiceCode) {
            return;
        }

        SubscriptionInvoice::where('provider_invoice_id', $invoiceCode)
            ->where('subscription_id', $subscription->id)
            ->update([
                'status' => $status,
                'paid_at' => $paidAt ? $this->parseDate($paidAt) : null,
                'meta' => array_merge(
                    SubscriptionInvoice::where('provider_invoice_id', $invoiceCode)->value('meta') ?? [],
                    ['paystack' => $data]
                ),
            ]);
    }

    private function findSubscriptionFromInvoice(array $data): ?Subscription
    {
        $subscriptionCode = Arr::get($data, 'subscription.subscription_code')
            ?? Arr::get($data, 'subscription_code');

        if (!$subscriptionCode) {
            return null;
        }

        return Subscription::query()
            ->where('provider', 'paystack')
            ->where('provider_subscription_id', $subscriptionCode)
            ->first();
    }

    private function parseDate(?string $value): ?Carbon
    {
        if (!$value) {
            return null;
        }

        try {
            return Carbon::parse($value);
        } catch (\Throwable $e) {
            Log::warning('Unable to parse date from Paystack payload', ['value' => $value]);
            return null;
        }
    }
}
