<?php

namespace App\Services;

use App\Models\Payment;
use App\Models\Webhook;
use App\Services\Pagarme\PagarmePaymentService;
use Illuminate\Support\Facades\Log;

class WebhookService
{
    private const PAYMENT_EVENTS = [
        'order.paid',
        'order.payment_failed',
        'order.canceled',
        'order.created',
        'order.closed',
        'order.updated',
        'charge.created',
        'charge.paid',
        'charge.payment_failed',
        'charge.refunded',
        'charge.pending',
        'charge.processing',
        'charge.underpaid',
        'charge.overpaid',
        'charge.partial_canceled',
        'charge.chargedback',
        'charge.updated',
    ];

    public function __construct(
        private readonly PagarmePaymentService     $pagarmePaymentService,
        private readonly PaymentService            $paymentService,
        private readonly ProviderWithdrawalService $providerWithdrawalService,
    ) {
    }

    public function handlePagarme(array $payload): ?Payment
    {
        $event = data_get($payload, 'type');
        $data  = data_get($payload, 'data', []);
        $data  = is_array($data) ? $data : [];

        $webhook = $this->recordWebhook($payload, $event, $data);

        Log::channel('pagarme')->info('Webhook do Pagar.me recebido', [
            'hook_id' => data_get($payload, 'id'),
            'event'   => $event,
        ]);

        if (! in_array($event, self::PAYMENT_EVENTS, true)) {
            if (str_starts_with((string) $event, 'transfer.')) {
                $this->providerWithdrawalService->handleTransferWebhook($data);

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

                return null;
            }

            $webhook->update([
                'status'       => 'ignored',
                'processed_at' => now(),
            ]);

            return null;
        }

        $orderResponse = $this->normalizeOrderResponse($event, $data);

        $payment = $this->findPayment($orderResponse);

        if (! $payment) {
            $webhook->update([
                'status'       => 'payment_not_found',
                'processed_at' => now(),
            ]);

            Log::channel('pagarme')->warning('Pagamento não encontrado para o webhook do Pagar.me', [
                'hook_id'        => data_get($payload, 'id'),
                'event'          => $event,
                'order_id'       => data_get($orderResponse, 'id'),
                'charge_id'      => data_get($orderResponse, 'charges.0.id'),
                'transaction_id' => data_get($orderResponse, 'charges.0.last_transaction.id'),
                'metadata'       => data_get($orderResponse, 'metadata', []),
            ]);

            return null;
        }

        try {
            $payment = $this->pagarmePaymentService->applyGatewayResponseToPayment($payment, $orderResponse);

            $this->paymentService->syncPaymentTargets($payment);

            $webhook->update([
                'payment_id'   => $payment->id,
                'status'       => 'processed',
                'processed_at' => now(),
            ]);

            return $payment;
        } catch (\Throwable $e) {
            $webhook->update([
                'payment_id'    => $payment->id,
                'status'        => 'failed',
                'processed_at'  => now(),
                'error_message' => $e->getMessage(),
            ]);

            throw $e;
        }
    }

    //

    private function recordWebhook(array $payload, ?string $event, array $data): Webhook
    {
        $references = $this->extractReferences($event, $data);

        $hookId = data_get($payload, 'id');

        $attributes = [
            'hook_id'        => $hookId,
            'provider'       => 'pagarme',
            'event'          => $event,
            'account_id'     => data_get($payload, 'account.id'),
            'order_id'       => data_get($references, 'order_id'),
            'charge_id'      => data_get($references, 'charge_id'),
            'transaction_id' => data_get($references, 'transaction_id'),
            'status'         => 'received',
            'payload'        => $payload,
            'received_at'    => now(),
            'processed_at'   => null,
            'error_message'  => null,
        ];

        if (empty($hookId)) {
            return Webhook::create($attributes);
        }

        $webhook = Webhook::firstOrNew([
            'provider' => 'pagarme',
            'hook_id'  => $hookId,
        ]);

        $webhook->fill([
            ...$attributes,
            'attempts_count' => $webhook->exists ? $webhook->attempts_count + 1 : 1,
        ])->save();

        return $webhook;
    }

    //

    private function extractReferences(?string $event, array $data): array
    {
        if (str_starts_with((string) $event, 'order.')) {
            $charge = data_get($data, 'charges.0', []);

            return [
                'order_id'       => data_get($data, 'id'),
                'charge_id'      => data_get($charge, 'id'),
                'transaction_id' => data_get($charge, 'last_transaction.id'),
            ];
        }

        if (str_starts_with((string) $event, 'charge.')) {
            return [
                'order_id'       => data_get($data, 'order.id', data_get($data, 'order_id')),
                'charge_id'      => data_get($data, 'id'),
                'transaction_id' => data_get($data, 'last_transaction.id'),
            ];
        }

        return [
            'order_id'       => null,
            'charge_id'      => null,
            'transaction_id' => null,
        ];
    }

    private function findPayment(array $orderResponse): ?Payment
    {
        $charge        = data_get($orderResponse, 'charges.0', []);
        $transaction   = data_get($charge, 'last_transaction', []);
        $metadata      = data_get($orderResponse, 'metadata', data_get($charge, 'metadata', []));
        $metadataId    = filter_var(data_get($metadata, 'payment_id'), FILTER_VALIDATE_INT) ?: null;
        $chargeId      = data_get($charge, 'id');
        $transactionId = data_get($transaction, 'id');
        $orderId       = data_get($orderResponse, 'id');
        $references    = array_filter([$metadataId, $chargeId, $transactionId, $orderId]);

        if (empty($references)) {
            return null;
        }

        return Payment::query()
            ->where('gateway_provider', 'pagarme')
            ->where(function ($query) use ($metadataId, $chargeId, $transactionId, $orderId) {
                $query
                    ->when($metadataId, fn ($query) => $query->orWhere('id', $metadataId))
                    ->when($chargeId, fn ($query) => $query->orWhere('gateway_entity_reference', $chargeId))
                    ->when($transactionId, fn ($query) => $query->orWhere('gateway_operation_reference', $transactionId))
                    ->when($orderId, fn ($query) => $query->orWhere(function ($query) use ($orderId) {
                        $query->where('gateway_entity_label', 'order')
                            ->where('gateway_entity_reference', $orderId);
                    }));
            })
            ->latest('id')
            ->first();
    }

    private function normalizeOrderResponse(?string $event, array $data): array
    {
        if (str_starts_with((string) $event, 'order.')) {
            return $data;
        }

        return [
            'id'       => data_get($data, 'order.id', data_get($data, 'order_id')),
            'metadata' => data_get($data, 'metadata', data_get($data, 'order.metadata', [])),
            'charges'  => [$data],
        ];
    }
}
