<?php

namespace App\Services;

use App\Enums\AccountFeeType;
use App\Enums\Currency;
use App\Enums\PaymentStatus;
use App\Models\Account;
use App\Models\Payment;
use App\Models\Sale;
use Illuminate\Support\Facades\DB;

class PaymentService
{
    /**
     * Calcula fee_amount y net_amount según el tipo de cuenta.
     * Regla Takenos: fee = (amount * 0.05) + 0.50  — SOLO USD
     */
    public function calculateFee(float $amount, Account $account, Currency $currency): array
    {
        // Takenos solo acepta USD
        if ($account->isTakenos() && $currency !== Currency::USD) {
            throw new \InvalidArgumentException(
                'La cuenta Takenos solo acepta pagos en USD.'
            );
        }

        $feeAmount = 0.0;

        if ($account->fee_type === AccountFeeType::Takenos) {
            // Fórmula fija del spec: (amount * 0.05) + 0.50
            $feeAmount = round(($amount * 0.05) + 0.50, 2);
        } elseif ($account->fee_type !== AccountFeeType::None) {
            // Para futuros tipos de fee con fee_percent + fee_fixed
            $feeAmount = round(
                ($amount * ((float) $account->fee_percent / 100)) + (float) $account->fee_fixed,
                2
            );
        }

        $netAmount = round($amount - $feeAmount, 2);

        return [
            'fee_amount' => $feeAmount,
            'net_amount' => $netAmount,
        ];
    }

    /**
     * Recalcula collected_amount_cached y payment_status en la venta.
     * Ejecutar dentro de una transacción DB.
     */
    public function recalculateSale(Sale $sale): void
    {
        // Usamos amount (bruto del cliente) para determinar si pagó en su totalidad.
        // Las comisiones de pasarela (Takenos, etc.) son costo operativo, no deuda del cliente.
        $collected   = $sale->payments()->sum('amount');
        $netExpected = (float) $sale->gross_amount - (float) $sale->platform_fee;

        $status = match(true) {
            $collected <= 0              => PaymentStatus::Unpaid,
            $collected < $netExpected    => PaymentStatus::Partial,
            default                      => PaymentStatus::Paid,
        };

        // Usamos update directo para no disparar el SaleObserver en loop
        Sale::withoutEvents(function () use ($sale, $collected, $status) {
            $sale->update([
                'collected_amount_cached' => round($collected, 2),
                'payment_status'          => $status,
            ]);
        });
    }

    /**
     * Procesa un pago completo: calcula fee, guarda y recalcula la venta.
     * Usa transacción DB para garantizar consistencia.
     */
    public function processPayment(Payment $payment): void
    {
        DB::transaction(function () use ($payment) {
            $account  = $payment->account;
            $currency = $payment->currency;
            $amount   = (float) $payment->amount;

            ['fee_amount' => $fee, 'net_amount' => $net] = $this->calculateFee($amount, $account, $currency);

            // Actualiza fee/net sin disparar observer de nuevo (ya estamos en él)
            Payment::withoutEvents(function () use ($payment, $fee, $net) {
                $payment->update(['fee_amount' => $fee, 'net_amount' => $net]);
            });

            $this->recalculateSale($payment->sale);
        });
    }

    /**
     * Recalcula la venta cuando se elimina un pago.
     */
    public function processPaymentDeleted(Sale $sale): void
    {
        DB::transaction(function () use ($sale) {
            $this->recalculateSale($sale);
        });
    }
}
