<?php

namespace App\Listeners;

use App\Enums\CreditLedgerEntryType;
use App\Enums\CreditProductType;
use App\Models\CreditLedgerEntry;
use App\Models\CreditProduct;
use App\Models\User;
use App\Services\AiCreditService;
use App\Services\PaymentInvoiceEmailer;
use App\Services\PaymentTransactionRecorder;
use Laravel\Cashier\Cashier;

class GrantCreditsFromStripeWebhook
{
    public function __construct(
        private AiCreditService $credits,
        private PaymentInvoiceEmailer $invoiceEmails,
        private PaymentTransactionRecorder $transactions,
    ) {}

    /**
     * @param  array{payload: array<string, mixed>}  $event
     */
    public function handle(object $event): void
    {
        $payload = $event->payload ?? [];

        match ($payload['type'] ?? null) {
            'checkout.session.completed' => $this->handleCheckoutSessionCompleted($payload),
            'checkout.session.expired' => $this->handleCheckoutSessionExpired($payload),
            'invoice.paid' => $this->handleInvoicePaid($payload),
            'invoice.payment_succeeded' => $this->handleInvoicePaid($payload),
            'invoice.payment_failed' => $this->handleInvoicePaymentFailed($payload),
            default => null,
        };
    }

    /**
     * @param  array<string, mixed>  $payload
     */
    private function handleCheckoutSessionCompleted(array $payload): void
    {
        $session = $payload['data']['object'] ?? [];
        $eventId = $this->stringValue($payload['id'] ?? null);
        $eventType = $this->stringValue($payload['type'] ?? null);
        $sessionId = $this->stringValue($session['id'] ?? null);

        if (! $sessionId) {
            return;
        }

        if (($session['mode'] ?? null) !== 'payment' || ($session['payment_status'] ?? null) !== 'paid') {
            $this->transactions->recordCheckoutSession(
                user: $this->userFromCustomer($session['customer'] ?? null),
                product: $this->productFromMetadata($session['metadata'] ?? null, CreditProductType::ONE_TIME),
                stripeCheckoutSessionId: $sessionId,
                status: \App\Enums\PaymentTransactionStatus::IGNORED,
                stripeEventId: $eventId,
                stripeEventType: $eventType,
                stripeCustomerId: $this->stringValue($session['customer'] ?? null),
                stripePaymentIntentId: $this->stringValue($session['payment_intent'] ?? null),
                amount: $this->intValue($session['amount_total'] ?? null),
                currency: $this->stringValue($session['currency'] ?? null),
                errorMessage: 'Checkout session is not a paid one-time payment.',
                payload: $session,
            );

            return;
        }

        $product = $this->productFromMetadata($session['metadata'] ?? null, CreditProductType::ONE_TIME);
        $user = $this->userFromCustomer($session['customer'] ?? null);

        if (! $product || ! $user) {
            $this->transactions->recordCheckoutSession(
                user: $user,
                product: $product,
                stripeCheckoutSessionId: $sessionId,
                status: \App\Enums\PaymentTransactionStatus::ERROR,
                stripeEventId: $eventId,
                stripeEventType: $eventType,
                stripeCustomerId: $this->stringValue($session['customer'] ?? null),
                stripePaymentIntentId: $this->stringValue($session['payment_intent'] ?? null),
                amount: $this->intValue($session['amount_total'] ?? null),
                currency: $this->stringValue($session['currency'] ?? null),
                errorMessage: $product
                    ? 'Stripe customer does not match any user.'
                    : 'Credit product was not found from checkout metadata.',
                payload: $session,
            );

            return;
        }

        $source = 'stripe_checkout_session:'.$sessionId;
        $entry = $this->credits->grant(
            user: $user,
            credits: $product->credits,
            type: CreditLedgerEntryType::PURCHASE,
            product: $product,
            source: $source,
            stripeCheckoutSessionId: $sessionId,
            stripePaymentIntentId: $this->stringValue($session['payment_intent'] ?? null),
            metadata: [
                'stripe_event_id' => $eventId,
            ],
        );

        $entry ??= CreditLedgerEntry::query()->where('source', $source)->first();
        $invoice = $this->retrieveInvoice($this->stringValue($session['invoice'] ?? null));

        $transaction = $this->transactions->recordCheckoutSession(
            user: $user,
            product: $product,
            stripeCheckoutSessionId: $sessionId,
            status: \App\Enums\PaymentTransactionStatus::CREDITED,
            stripeEventId: $eventId,
            stripeEventType: $eventType,
            stripeCustomerId: $this->stringValue($session['customer'] ?? null),
            stripePaymentIntentId: $this->stringValue($session['payment_intent'] ?? null),
            amount: $this->intValue($session['amount_total'] ?? null),
            currency: $this->stringValue($session['currency'] ?? null),
            creditsGranted: $entry ? $product->credits : 0,
            ledgerEntry: $entry,
            invoiceUrl: $invoice ? $this->stringValue($invoice['hosted_invoice_url'] ?? null) : null,
            invoicePdfUrl: $invoice ? $this->stringValue($invoice['invoice_pdf'] ?? null) : null,
            payload: $session,
        );

        $this->invoiceEmails->sendIfAvailable($transaction);
    }

    /**
     * @param  array<string, mixed>  $payload
     */
    private function handleCheckoutSessionExpired(array $payload): void
    {
        $session = $payload['data']['object'] ?? [];
        $sessionId = $this->stringValue($session['id'] ?? null);

        if (! $sessionId) {
            return;
        }

        $this->transactions->recordCheckoutSession(
            user: $this->userFromCustomer($session['customer'] ?? null),
            product: $this->productFromMetadata($session['metadata'] ?? null, CreditProductType::ONE_TIME),
            stripeCheckoutSessionId: $sessionId,
            status: \App\Enums\PaymentTransactionStatus::FAILED,
            stripeEventId: $this->stringValue($payload['id'] ?? null),
            stripeEventType: $this->stringValue($payload['type'] ?? null),
            stripeCustomerId: $this->stringValue($session['customer'] ?? null),
            stripePaymentIntentId: $this->stringValue($session['payment_intent'] ?? null),
            amount: $this->intValue($session['amount_total'] ?? null),
            currency: $this->stringValue($session['currency'] ?? null),
            errorMessage: 'Checkout session expired before payment.',
            payload: $session,
        );
    }

    /**
     * @param  array<string, mixed>  $payload
     */
    private function handleInvoicePaid(array $payload): void
    {
        $invoice = $payload['data']['object'] ?? [];
        $invoiceId = $this->stringValue($invoice['id'] ?? null);
        $eventId = $this->stringValue($payload['id'] ?? null);
        $eventType = $this->stringValue($payload['type'] ?? null);

        if (! $invoiceId) {
            return;
        }

        $user = $this->userFromCustomer($invoice['customer'] ?? null);

        if (! $user) {
            $this->transactions->recordInvoice(
                user: null,
                product: null,
                stripeInvoiceId: $invoiceId,
                stripePriceId: null,
                status: \App\Enums\PaymentTransactionStatus::ERROR,
                stripeEventId: $eventId,
                stripeEventType: $eventType,
                stripeCustomerId: $this->stringValue($invoice['customer'] ?? null),
                stripePaymentIntentId: $this->stringValue($invoice['payment_intent'] ?? null),
                stripeSubscriptionId: $this->stringValue($invoice['subscription'] ?? null),
                amount: $this->intValue($invoice['amount_paid'] ?? null),
                currency: $this->stringValue($invoice['currency'] ?? null),
                errorMessage: 'Stripe customer does not match any user.',
                payload: $invoice,
            );

            return;
        }

        $matchedLine = false;

        foreach (($invoice['lines']['data'] ?? []) as $line) {
            $priceId = $line['price']['id'] ?? null;

            if (! $priceId) {
                continue;
            }

            $product = CreditProduct::query()
                ->where('type', CreditProductType::MONTHLY)
                ->where('stripe_price_id', $priceId)
                ->first();

            if (! $product) {
                $this->transactions->recordInvoice(
                    user: $user,
                    product: null,
                    stripeInvoiceId: $invoiceId,
                    stripePriceId: $this->stringValue($priceId),
                    status: \App\Enums\PaymentTransactionStatus::ERROR,
                    stripeEventId: $eventId,
                    stripeEventType: $eventType,
                    stripeCustomerId: $this->stringValue($invoice['customer'] ?? null),
                    stripePaymentIntentId: $this->stringValue($invoice['payment_intent'] ?? null),
                    stripeSubscriptionId: $this->stringValue($invoice['subscription'] ?? null),
                    amount: $this->intValue($line['amount'] ?? $invoice['amount_paid'] ?? null),
                    currency: $this->stringValue($invoice['currency'] ?? null),
                    errorMessage: 'No monthly credit product found for Stripe price.',
                    payload: $invoice,
                );

                continue;
            }

            $matchedLine = true;
            $source = 'stripe_invoice:'.$invoiceId.':price:'.$priceId;
            $entry = $this->credits->grant(
                user: $user,
                credits: $product->credits,
                type: CreditLedgerEntryType::SUBSCRIPTION_RENEWAL,
                product: $product,
                source: $source,
                stripeInvoiceId: $invoiceId,
                stripePaymentIntentId: $this->stringValue($invoice['payment_intent'] ?? null),
                stripeSubscriptionId: $this->stringValue($invoice['subscription'] ?? null),
                metadata: [
                    'billing_reason' => $invoice['billing_reason'] ?? null,
                    'stripe_event_id' => $eventId,
                ],
            );

            $entry ??= CreditLedgerEntry::query()->where('source', $source)->first();

            $transaction = $this->transactions->recordInvoice(
                user: $user,
                product: $product,
                stripeInvoiceId: $invoiceId,
                stripePriceId: $this->stringValue($priceId),
                status: \App\Enums\PaymentTransactionStatus::CREDITED,
                stripeEventId: $eventId,
                stripeEventType: $eventType,
                stripeCustomerId: $this->stringValue($invoice['customer'] ?? null),
                stripePaymentIntentId: $this->stringValue($invoice['payment_intent'] ?? null),
                stripeSubscriptionId: $this->stringValue($invoice['subscription'] ?? null),
                amount: $this->intValue($line['amount'] ?? $invoice['amount_paid'] ?? null),
                currency: $this->stringValue($invoice['currency'] ?? null),
                creditsGranted: $entry ? $product->credits : 0,
                ledgerEntry: $entry,
                invoiceUrl: $this->stringValue($invoice['hosted_invoice_url'] ?? null),
                invoicePdfUrl: $this->stringValue($invoice['invoice_pdf'] ?? null),
                payload: $invoice,
            );

            $this->invoiceEmails->sendIfAvailable($transaction);
        }

        if (! $matchedLine) {
            $this->transactions->recordInvoice(
                user: $user,
                product: null,
                stripeInvoiceId: $invoiceId,
                stripePriceId: null,
                status: \App\Enums\PaymentTransactionStatus::IGNORED,
                stripeEventId: $eventId,
                stripeEventType: $eventType,
                stripeCustomerId: $this->stringValue($invoice['customer'] ?? null),
                stripePaymentIntentId: $this->stringValue($invoice['payment_intent'] ?? null),
                stripeSubscriptionId: $this->stringValue($invoice['subscription'] ?? null),
                amount: $this->intValue($invoice['amount_paid'] ?? null),
                currency: $this->stringValue($invoice['currency'] ?? null),
                errorMessage: 'Invoice does not contain a monthly credit product line.',
                payload: $invoice,
            );
        }
    }

    /**
     * @param  array<string, mixed>  $payload
     */
    private function handleInvoicePaymentFailed(array $payload): void
    {
        $invoice = $payload['data']['object'] ?? [];
        $invoiceId = $this->stringValue($invoice['id'] ?? null);

        if (! $invoiceId) {
            return;
        }

        foreach (($invoice['lines']['data'] ?? [null]) as $line) {
            $priceId = is_array($line) ? $this->stringValue($line['price']['id'] ?? null) : null;
            $amount = is_array($line)
                ? $this->intValue($line['amount'] ?? $invoice['amount_due'] ?? null)
                : $this->intValue($invoice['amount_due'] ?? null);
            $product = $priceId
                ? CreditProduct::query()->where('stripe_price_id', $priceId)->first()
                : null;

            $this->transactions->recordInvoice(
                user: $this->userFromCustomer($invoice['customer'] ?? null),
                product: $product,
                stripeInvoiceId: $invoiceId,
                stripePriceId: $priceId,
                status: \App\Enums\PaymentTransactionStatus::FAILED,
                stripeEventId: $this->stringValue($payload['id'] ?? null),
                stripeEventType: $this->stringValue($payload['type'] ?? null),
                stripeCustomerId: $this->stringValue($invoice['customer'] ?? null),
                stripePaymentIntentId: $this->stringValue($invoice['payment_intent'] ?? null),
                stripeSubscriptionId: $this->stringValue($invoice['subscription'] ?? null),
                amount: $amount,
                currency: $this->stringValue($invoice['currency'] ?? null),
                errorMessage: 'Invoice payment failed.',
                payload: $invoice,
            );
        }
    }

    private function userFromCustomer(mixed $customer): ?User
    {
        if (! is_string($customer) || $customer === '') {
            return null;
        }

        return User::query()->where('stripe_id', $customer)->first();
    }

    private function productFromMetadata(mixed $metadata, CreditProductType $type): ?CreditProduct
    {
        if (! is_array($metadata) || ! is_string($metadata['credit_product_id'] ?? null)) {
            return null;
        }

        return CreditProduct::query()
            ->whereKey($metadata['credit_product_id'])
            ->where('type', $type)
            ->first();
    }

    private function stringValue(mixed $value): ?string
    {
        return is_string($value) && $value !== '' ? $value : null;
    }

    private function intValue(mixed $value): ?int
    {
        return is_numeric($value) ? (int) $value : null;
    }

    /**
     * @return array<string, mixed>|null
     */
    private function retrieveInvoice(?string $invoiceId): ?array
    {
        if (! $invoiceId) {
            return null;
        }

        try {
            return Cashier::stripe()
                ->invoices
                ->retrieve($invoiceId)
                ->toArray();
        } catch (\Throwable) {
            return null;
        }
    }
}
