<?php

declare(strict_types=1);

namespace App\Services\Credits\Operations;

use App\DTOs\CreditOperationResult;
use App\DTOs\RenewCreditData;
use App\Enums\CreditAuditEvent;
use App\Models\Credit;
use App\Models\CreditRestructureLog;
use App\ValueObjects\Money;

/**
 * Operación de RENOVACIÓN de crédito.
 *
 * DEFINICIÓN:
 * Nuevo crédito que PAGA TOTALMENTE el anterior y entrega la diferencia al cliente.
 * Requiere que el cliente haya realizado pagos previos.
 *
 * ECUACIÓN FINANCIERA:
 *   nuevo_principal = monto_solicitado
 *   cash_out = monto_solicitado - saldo_pendiente (la diferencia)
 *   compensated_amount = saldo_pendiente
 *
 * ESTADOS:
 *   Entrada: active, delayed (con pagos realizados)
 *   Salida original: renewed
 *   Salida nuevo: active
 */
class RenewCreditOperation extends BaseCreditOperation
{
    /**
     * Ejecuta la renovación del crédito.
     *
     * PRECONDICIÓN: Validación ya realizada por CreditOperationValidator
     *
     * @param  Credit  $credit  Crédito a renovar (locked for update)
     * @param  RenewCreditData  $data  Datos de la renovación
     * @param  Money  $remainingBalance  Saldo pendiente calculado
     */
    public function execute(
        Credit $credit,
        RenewCreditData $data,
        Money $remainingBalance,
    ): CreditOperationResult {
        // 1. El nuevo principal es el monto solicitado
        $newPrincipal = $data->newCreditAmount;

        // 2. El desembolso es la diferencia (nuevo monto - saldo pendiente)
        $cashOut = $newPrincipal->subtract($remainingBalance);

        // 3. Crear nuevo crédito hijo con el monto completo
        $newCredit = $this->createChildCredit(
            originalCredit: $credit,
            principal: $newPrincipal,
            installmentsCount: $data->newInstallmentsCount,
            periodicity: $data->periodicity,
            startDate: $data->startDate,
            firstDueDateOverride: $data->firstDueDateOverride,
            dueDay1: $data->dueDay1,
            dueDay2: $data->dueDay2,
            interestRate: $data->newInterestRate,
        );

        // 4. Registrar desembolso de la diferencia (solo si hay diferencia)
        $expense = null;
        if ($cashOut->isPositive()) {
            $expense = $this->financeAutoLogger->logExpense([
                'company_id' => $newCredit->company_id,
                'user_id' => auth()->id(),
                'collector_user_id' => $newCredit->collector_user_id,
                'amount' => $cashOut->value(),
                'credit_id' => $newCredit->id,
                'category' => 'disbursement',
                'operation_date' => $data->startDate ?? now(),
                'notes' => sprintf(
                    'Desembolso por renovación - Diferencia del crédito #%d',
                    $credit->id
                ),
                'metadata' => [
                    'source' => 'credit_renewed',
                    'original_credit_id' => $credit->id,
                    'previous_balance' => $remainingBalance->value(),
                    'new_credit_amount' => $newPrincipal->value(),
                    'cash_out' => $cashOut->value(),
                ],
            ]);
        }

        // 5. Cerrar crédito original con estado 'renewed'
        $this->closeOriginalCredit($credit, Credit::STATUS_RENEWED);

        // 6. Registrar en credit_restructure_logs
        $restructureLog = $this->logRestructure(
            originalCredit: $credit,
            newCredit: $newCredit,
            type: CreditRestructureLog::TYPE_RENEWAL,
            previousBalance: $remainingBalance,
            newPrincipal: $newPrincipal,
            additionalCapital: $cashOut, // La diferencia es el capital adicional
            capitalizedInterest: Money::zero(),
            newInterestRate: $data->newInterestRate,
            newInstallmentCount: $data->newInstallmentsCount,
            newPeriodicity: $data->periodicity,
            reason: $data->reason,
            metadata: [
                'operation' => 'renewal',
                'input_data' => $data->toArray(),
                'expense_id' => $expense?->id,
                'client_received' => $cashOut->value(),
            ],
        );

        // 7. Registrar operación financiera
        $financialOperation = $this->logFinancialOperation(
            originalCredit: $credit,
            newCredit: $newCredit,
            operationType: 'credit_renewal',
            creditAmount: $newPrincipal,
            compensatedAmount: $remainingBalance,
            cashOut: $cashOut,
            expenseId: $expense?->id,
            notes: sprintf(
                'Renovación - Nuevo monto: %s, Saldo compensado: %s, Desembolso: %s',
                $newPrincipal->formatWithCurrency(),
                $remainingBalance->formatWithCurrency(),
                $cashOut->formatWithCurrency()
            ),
        );

        // 8. Registrar auditoría
        $this->logAudit(
            creditId: $credit->id,
            action: CreditAuditEvent::CREDIT_RENEWED,
            data: array_merge($data->toArray(), [
                'previous_balance' => $remainingBalance->value(),
                'new_principal' => $newPrincipal->value(),
                'cash_out' => $cashOut->value(),
                'new_credit_id' => $newCredit->id,
                'expense_id' => $expense?->id,
            ]),
            newCreditId: $newCredit->id,
        );

        // 9. Construir y retornar resultado
        return $this->buildResult(
            originalCredit: $credit,
            newCredit: $newCredit,
            restructureLog: $restructureLog,
            financialOperation: $financialOperation,
            previousBalance: $remainingBalance,
            newPrincipal: $newPrincipal,
            cashOut: $cashOut,
            compensatedAmount: $remainingBalance,
            operationType: 'renewal',
        );
    }
}
