<?php

namespace App\Domains\Integration\Services;

use App\Domains\Client\Models\Client;
use App\Domains\Purchase\Models\Purchase;
use App\Domains\Purchase\Models\TelepagosPayment;
use App\Domains\Purchase\Models\TelepagosQr;
use App\Domains\Purchase\Services\CheckoutService;
use App\Domains\Purchase\Services\DniDistanceService;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class TelepagosWebhookService
{
    public function __construct(
        private readonly CheckoutService $checkoutService,
        private readonly DniDistanceService $dniDistance,
    ) {}

    /**
     * Handle the Telepagos webhook notification.
     *
     * @throws Exception
     */
    public function handleWebhook(Client $client, string $cashinId): void
    {
        Log::channel('telepagos')->info('Telepagos webhook received.', [
            'client_code' => $client->code,
            'cashin_id' => $cashinId,
        ]);

        $telepagosService = new TelepagosIntegrationService;
        $telepagosService->forClient($client);

        try {
            $details = $telepagosService->getCashinDetails($cashinId);

            $qrOrderId = $details['data']['qr_order_id'] ?? $details['qr_order_id'] ?? null;
            $amount = $this->normalizeAmount($details['data']['amount'] ?? $details['amount'] ?? 0);
            $operationId = $details['data']['operation_id'] ?? $details['operation_id'] ?? null;

            $paymentData = [
                'compra_id' => null,
                'cuit_buyer' => $details['data']['buyer']['cuit'] ?? $details['buyer']['cuit'] ?? null,
                'cvu_buyer' => $details['data']['buyer']['cvu'] ?? $details['buyer']['cvu'] ?? null,
                'amount' => $amount,
                'concept' => $details['data']['concept'] ?? $details['concept'] ?? null,
                'operation' => $details['data']['operation'] ?? $details['operation'] ?? null,
                'operation_id' => $details['data']['operation_id'] ?? $details['operation_id'] ?? null,
                'transaction_id' => $details['data']['transaction_id'] ?? $details['transaction_id'] ?? null,
                'qr_order_id' => $qrOrderId,
                'link_id' => $details['data']['link_id'] ?? $details['link_id'] ?? null,
            ];

            $transferenciaOperationIds = [1, 3, 11];
            $qrOperationIds = [31, 37, 47];

            $compra = null;

            if (in_array((int) $operationId, $transferenciaOperationIds, true)) {
                $cuit = $details['data']['buyer']['cuit'] ?? $details['buyer']['cuit'] ?? null;

                if (! $cuit) {
                    Log::channel('telepagos')->warning('Telepagos webhook: CUIT not found for transfer.', [
                        'client_code' => $client->code,
                        'cashin_id' => $cashinId,
                    ]);

                    return;
                }

                $dni = substr($cuit, 2, -1);

                $tenantCodes = $client->tenants()->pluck('codigo');
                $eligiblePurchases = Purchase::query()
                    ->whereIn('tenant_codigo', $tenantCodes)
                    ->whereIn('status', [
                        Purchase::STATUS_CREATED,
                        Purchase::STATUS_PENDING_PAYMENT,
                        Purchase::STATUS_IN_REVIEW,
                    ])
                    ->where('payment_method', 'transfer');

                $purchases = (clone $eligiblePurchases)
                    ->where('total', $amount)
                    ->latest()
                    ->get()
                    ->filter(fn (Purchase $purchase): bool => $purchase->transfer_payer_dni !== null
                        && $this->dniDistance->distance($dni, $purchase->transfer_payer_dni) === 0)
                    ->values();

                $compra = $purchases->count() === 1 ? $purchases->first() : null;

                if (! $compra) {
                    $candidatePurchases = $this->findTransferCandidates(
                        $eligiblePurchases,
                        $dni,
                        $amount,
                    );
                    if ($candidatePurchases->isNotEmpty()) {
                        $payment = $this->storeTransferCandidates(
                            $paymentData,
                            $candidatePurchases,
                            $dni,
                            $amount,
                        );

                        Log::channel('telepagos')->info('Telepagos webhook: Transfer payment candidates found.', [
                            'client_code' => $client->code,
                            'cashin_id' => $cashinId,
                            'telepagos_payment_id' => $payment->id,
                            'amount' => $amount,
                            'candidate_count' => $payment->candidates->count(),
                            'candidates' => $payment->candidates
                                ->map(fn ($candidate): array => [
                                    'purchase_id' => $candidate->compra_id,
                                    'match_reason' => $candidate->match_reason,
                                    'dni_distance' => $candidate->dni_distance,
                                    'amount_difference' => $candidate->amount_difference,
                                    'confidence' => $candidate->confidence,
                                ])
                                ->all(),
                        ]);
                    }

                    Log::channel('telepagos')->warning('Telepagos webhook: Expected exactly one matching purchase.', [
                        'client_code' => $client->code,
                        'cashin_id' => $cashinId,
                        'amount' => $amount,
                        'matches' => $purchases->count(),
                    ]);

                    return;
                }
            } elseif (in_array((int) $operationId, $qrOperationIds, true)) {
                if (! $qrOrderId) {
                    Log::channel('telepagos')->warning('Telepagos webhook: qr_order_id not present in provider response.', [
                        'client_code' => $client->code,
                        'cashin_id' => $cashinId,
                    ]);

                    return;
                }

                $telepagosQr = TelepagosQr::where('qr_order_id', $qrOrderId)->first();

                if (! $telepagosQr) {
                    Log::channel('telepagos')->warning('Telepagos webhook: QR not found in database.', [
                        'client_code' => $client->code,
                        'cashin_id' => $cashinId,
                        'qr_order_id' => $qrOrderId,
                    ]);

                    return;
                }

                $compra = $telepagosQr->compra;

                if (! $compra) {
                    Log::channel('telepagos')->warning('Telepagos webhook: Purchase not found for QR.', [
                        'client_code' => $client->code,
                        'cashin_id' => $cashinId,
                        'qr_order_id' => $qrOrderId,
                    ]);

                    return;
                }

                if (! $client->tenants()->where('codigo', $compra->tenant_codigo)->exists()) {
                    Log::channel('telepagos')->warning('Telepagos webhook: Purchase does not belong to client.', [
                        'client_code' => $client->code,
                        'cashin_id' => $cashinId,
                        'purchase_id' => $compra->id,
                    ]);

                    return;
                }

                if (! in_array($compra->status, [
                    Purchase::STATUS_PENDING_PAYMENT,
                ], true)) {
                    Log::channel('telepagos')->warning('Telepagos webhook: Purchase is not awaiting payment confirmation.', [
                        'client_code' => $client->code,
                        'cashin_id' => $cashinId,
                        'purchase_id' => $compra->id,
                        'purchase_status' => $compra->status,
                    ]);

                    return;
                }

                $totalAmount = $this->normalizeAmount($compra->getTotalAmount());

                if ($amount !== $totalAmount) {
                    Log::channel('telepagos')->warning('Telepagos webhook: Amount mismatch.', [
                        'client_code' => $client->code,
                        'cashin_id' => $cashinId,
                        'purchase_id' => $compra->id,
                        'cashin_amount' => $amount,
                        'purchase_amount' => $totalAmount,
                    ]);

                    return;
                }
            } else {
                Log::channel('telepagos')->warning('Telepagos webhook: Unknown operation_id.', [
                    'client_code' => $client->code,
                    'cashin_id' => $cashinId,
                    'operation_id' => $operationId,
                ]);

                return;
            }

            $paymentData['compra_id'] = $compra->id;

            DB::transaction(function () use ($compra, $paymentData) {
                TelepagosPayment::create($paymentData);
                $this->checkoutService->confirmPaidPurchase($compra);
            });

            Log::channel('telepagos')->info('Telepagos webhook processed successfully.', [
                'client_code' => $client->code,
                'cashin_id' => $cashinId,
                'purchase_id' => $compra->id,
                'transaction_id' => $paymentData['transaction_id'],
            ]);
        } catch (Exception $e) {
            Log::channel('telepagos')->error('Telepagos webhook processing failed.', [
                'client_code' => $client->code,
                'cashin_id' => $cashinId,
                'error' => $e->getMessage(),
            ]);
            throw $e;
        }
    }

    protected function normalizeAmount(mixed $amount): string
    {
        return number_format((float) $amount, 2, '.', '');
    }

    /**
     * @param  Builder<Purchase>  $eligiblePurchases
     * @return Collection<int, Purchase>
     */
    private function findTransferCandidates(Builder $eligiblePurchases, string $dni, string $amount): Collection
    {
        $tolerancePercentage = max(
            0,
            (float) config('purchase.transfer_candidate_amount_tolerance_percentage', 5),
        );
        $numericAmount = (float) $amount;
        $tolerance = $numericAmount * ($tolerancePercentage / 100);
        $minimumAmount = $this->normalizeAmount(max(0, $numericAmount - $tolerance));
        $maximumAmount = $this->normalizeAmount($numericAmount + $tolerance);

        return (clone $eligiblePurchases)
            ->whereBetween('total', [$minimumAmount, $maximumAmount])
            ->latest()
            ->get()
            ->filter(function (Purchase $purchase) use ($dni, $amount): bool {
                if ($purchase->transfer_payer_dni === null) {
                    return false;
                }

                $purchaseAmount = $this->normalizeAmount($purchase->total);
                $distance = $this->dniDistance->distance(
                    $dni,
                    (string) $purchase->transfer_payer_dni,
                );

                return $distance === 0
                    || ($purchaseAmount === $amount && $distance <= 2);
            })
            ->values();
    }

    /**
     * @param  array<string, mixed>  $paymentData
     * @param  Collection<int, Purchase>  $candidatePurchases
     */
    private function storeTransferCandidates(
        array $paymentData,
        Collection $candidatePurchases,
        string $dni,
        string $amount,
    ): TelepagosPayment {
        return DB::transaction(function () use ($paymentData, $candidatePurchases, $dni, $amount): TelepagosPayment {
            $payment = TelepagosPayment::create($paymentData);

            $payment->candidates()->createMany(
                $candidatePurchases
                    ->map(function (Purchase $purchase) use ($dni, $amount): array {
                        $purchaseAmount = $this->normalizeAmount($purchase->total);
                        $dniDistance = $this->dniDistance->distance(
                            $dni,
                            (string) $purchase->transfer_payer_dni,
                        );
                        $dniMatches = $dniDistance === 0;
                        $amountMatches = $purchaseAmount === $amount;

                        return [
                            'compra_id' => $purchase->id,
                            'dni_matches' => $dniMatches,
                            'dni_distance' => $dniDistance,
                            'payment_dni' => $dni,
                            'purchase_dni' => $purchase->transfer_payer_dni,
                            'amount_matches' => $amountMatches,
                            'payment_amount' => $amount,
                            'purchase_amount' => $purchaseAmount,
                            'amount_difference' => $this->normalizeAmount(
                                abs((float) $purchaseAmount - (float) $amount),
                            ),
                            'match_reason' => $amountMatches
                                ? ($dniMatches ? 'ambiguous_exact_match' : 'exact_amount_different_dni')
                                : 'exact_dni_near_amount',
                            'confidence' => $amountMatches
                                ? ($dniMatches ? 'exact' : 'medium')
                                : 'high',
                        ];
                    })
                    ->all(),
            );

            return $payment->load('candidates');
        });
    }
}
