<?php

namespace App\Services;

use App\Models\Credit;
use App\Models\Income;
use App\Models\Installment;
use App\Models\Payment;
use App\Models\PaymentAuditLog;
use App\Models\User;
use App\Services\Payments\PaymentDistributor;
use App\Services\Payments\PaymentMaterializer;
use App\Services\Payments\PaymentRegistrar;
use App\Services\Payments\PaymentReverser;
use App\ValueObjects\Money;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;

/**
 * Orquestador central para operaciones de pagos.
 * Delega la lógica a servicios especializados.
 */
class PaymentManager
{
    public function __construct(
        protected PaymentRegistrar $registrar,
        protected PaymentReverser $reverser,
        protected PaymentDistributor $distributor,
        protected PaymentMaterializer $materializer
    ) {}

    /*==============================================================
     | REGISTRAR PAGO (dispatcher)
     ===============================================================*/
    public function registerPayment(
        float $amount,
        Carbon $paymentDate,
        int $registeredByUserId,
        ?Installment $installment = null,
        ?Credit $credit = null,
        ?string $paymentMethod = null,
    ): Payment {
        return $this->registrar->register(
            $amount,
            $paymentDate,
            $registeredByUserId,
            $installment,
            $credit,
            $paymentMethod
        );
    }

    /*==============================================================
     | REGISTRAR PAGO – CRÉDITO COMPLETO
     ===============================================================*/
    public function registerPaymentForCredit(
        Credit $credit,
        float $amount,
        Carbon $paymentDate,
        int $registeredByUserId,
        ?string $paymentMethod = null,
    ): Payment {
        return $this->registrar->forCredit(
            $credit,
            $amount,
            $paymentDate,
            $registeredByUserId,
            $paymentMethod
        );
    }

    /*==============================================================
     | REGISTRAR PAGO – CUOTA PREFERIDA
     ===============================================================*/
    public function registerPaymentForInstallment(
        Installment $installment,
        float $amount,
        Carbon $paymentDate,
        int $registeredByUserId,
        ?string $paymentMethod = null,
    ): Payment {
        return $this->registrar->forInstallment(
            $installment,
            $amount,
            $paymentDate,
            $registeredByUserId,
            $paymentMethod
        );
    }

    /*==============================================================
    |  REVERTIR (ANULAR) UN PAGO
    ===============================================================*/
    public function reversePayment(Payment $payment, User $user, string $reason): Payment
    {
        return $this->reverser->reverse($payment, $user, $reason);
    }

    /*==============================================================
    |  ELIMINAR (REVERSAR) UN PAGO – Interfaz para Filament/UI
    |  En sistemas financieros, "eliminar" = "reversar" para auditoría
    ===============================================================*/
    public function deletePayment(Payment $payment, string $reason): Payment
    {
        $user = auth()->user();

        return $this->reverser->reverse($payment, $user, $reason);
    }

    /*==============================================================
    |  ACTUALIZAR PAGO – Actualiza installments, income y auditoría
    ===============================================================*/
    public function updatePayment(Payment $payment, array $data): Payment
    {
        if ($payment->voided) {
            throw new InvalidArgumentException('No se puede editar un pago anulado.');
        }

        // Campos permitidos para edición
        $allowedFields = ['amount', 'payment_method', 'payment_date'];
        $updateData = array_intersect_key($data, array_flip($allowedFields));

        if (empty($updateData)) {
            return $payment;
        }

        return DB::transaction(function () use ($payment, $updateData) {
            // Lock para evitar condiciones de carrera
            $payment = Payment::where('id', $payment->id)
                ->where('company_id', $payment->company_id)
                ->lockForUpdate()
                ->firstOrFail();

            $credit = Credit::where('id', $payment->credit_id)
                ->where('company_id', $payment->company_id)
                ->lockForUpdate()
                ->firstOrFail();

            // Snapshot antes de cambios
            $before = $this->snapshotContext($payment, $credit);

            // Determinar si el monto cambió (requiere redistribución)
            $oldAmount = Money::of($payment->amount);
            $newAmount = isset($updateData['amount'])
                ? Money::of($updateData['amount'])
                : $oldAmount;
            $amountChanged = ! $oldAmount->equals($newAmount);

            // Determinar si la fecha cambió
            $oldDate = $payment->payment_date;
            $newDate = isset($updateData['payment_date'])
                ? Carbon::parse($updateData['payment_date'])
                : $oldDate;

            // Actualizar campos del pago
            if (isset($updateData['amount'])) {
                $payment->amount = $newAmount->value();
            }
            if (isset($updateData['payment_date'])) {
                $payment->payment_date = $newDate->toDateString();
            }
            if (isset($updateData['payment_method'])) {
                $payment->payment_method = $updateData['payment_method'];
            }
            $payment->save();

            // Si el monto cambió, redistribuir el pago entre cuotas
            if ($amountChanged) {
                $preferredInstallment = $payment->installment_id
                    ? Installment::find($payment->installment_id)
                    : null;

                $this->distributor->distribute(
                    $payment,
                    $credit,
                    $payment->amount,
                    $newDate instanceof Carbon ? $newDate : Carbon::parse($newDate),
                    $preferredInstallment
                );
            }

            // Rematerializar totales del crédito (actualiza installments)
            $this->materializer->materializeCredit($credit->fresh());
            $payment->refresh();

            // Sincronizar incomes (caja)
            $this->syncIncomesForPayment($payment);

            // Snapshot después de cambios
            $after = $this->snapshotContext($payment->fresh(), $credit->fresh());

            // Auditoría
            PaymentAuditLog::create([
                'payment_id' => $payment->id,
                'credit_id' => $credit->id,
                'installment_id' => $payment->installment_id,
                'company_id' => $payment->company_id,
                'user_id' => auth()->id(),
                'action' => 'updated',
                'amount' => $payment->amount,
                'previous_data' => $before,
                'new_data' => $after,
            ]);

            return $payment->fresh();
        });
    }

    /*==============================================================
    |  SINCRONIZAR INCOMES PARA UN PAGO (Caja)
    ===============================================================*/
    protected function syncIncomesForPayment(Payment $payment): void
    {
        // Eliminar incomes anteriores (bypass del modelo para evitar protección)
        Income::where('payment_id', $payment->id)
            ->where('company_id', $payment->company_id)
            ->delete();

        // Recrear income para applied_amount
        if (! $payment->voided && $payment->applied_amount > 0) {
            app(FinanceAutoLogger::class)->logIncome([
                'company_id' => $payment->company_id,
                'user_id' => $payment->registered_by_user_id,
                'amount' => $payment->applied_amount,
                'category' => 'payment',
                'credit_id' => $payment->credit_id,
                'payment_id' => $payment->id,
                'operation_date' => $payment->payment_date,
            ]);
        }

        // Recrear income para extra_amount (overpayment)
        if (! $payment->voided && $payment->extra_amount > 0) {
            app(FinanceAutoLogger::class)->logIncome([
                'company_id' => $payment->company_id,
                'user_id' => $payment->registered_by_user_id,
                'amount' => $payment->extra_amount,
                'category' => 'overpayment',
                'credit_id' => $payment->credit_id,
                'payment_id' => $payment->id,
                'operation_date' => $payment->payment_date,
                'metadata' => [
                    'type' => 'overpayment',
                    'source' => 'payment_excess',
                    'original_payment_amount' => $payment->amount,
                    'applied_amount' => $payment->applied_amount,
                ],
            ]);
        }
    }

    /*==============================================================
    |  SNAPSHOT DE CONTEXTO PARA AUDITORÍA
    ===============================================================*/
    protected function snapshotContext(Payment $payment, Credit $credit): array
    {
        return [
            'payment' => $payment->toArray(),
            'credit' => $credit->toArray(),
            'installments' => $credit->installments()->get()->toArray(),
        ];
    }

    /*==============================================================
    |  VALIDACIÓN ADMINISTRATIVA – ELIMINACIÓN DE PAGOS
    |  (Usado por Filament / UI)
    ===============================================================*/
    public function canDeletePayment(Payment $payment): bool
    {
        // Nunca eliminar pagos ya anulados
        if ($payment->voided) {
            return false;
        }

        // No permitir eliminar pagos de créditos cerrados
        if (
            $payment->credit &&
            $payment->credit->status === Credit::STATUS_PAID
        ) {
            return false;
        }

        // Protección extra: pagos con auditoría crítica
        if (
            PaymentAuditLog::where('payment_id', $payment->id)
                ->where('company_id', $payment->company_id)
                ->whereIn('action', ['reversed', 'voided'])
                ->exists()
        ) {
            return false;
        }

        return true;
    }
}
