<?php

declare(strict_types=1);

namespace App\Actions;

use App\DTOs\CreditOperationResult;
use App\DTOs\RestructureWithCustomPlanData;
use App\Enums\CreditAuditEvent;
use App\Models\Credit;
use App\Models\CreditAuditLog;
use App\Models\CreditRestructureLog;
use App\Models\FinancialOperation;
use App\Services\CreditManager;
use App\Services\Credits\IrregularInstallmentGenerator;
use App\Validators\CustomInstallmentPlanValidator;
use App\ValueObjects\Money;
use Exception;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;

/**
 * Ejecuta reestructuración de crédito con plan de cuotas personalizado.
 *
 * Esta action permite reestructurar un crédito con cuotas de montos
 * irregulares (diferentes entre sí), a diferencia de la reestructuración
 * estándar que calcula cuotas iguales.
 *
 * Características:
 * - NO modifica el crédito original ni sus cuotas
 * - Crea un crédito hijo vinculado (parent_credit_id)
 * - Valida que la suma de cuotas = saldo pendiente
 * - Registra auditoría completa (CreditRestructureLog, CreditAuditLog, FinancialOperation)
 * - El crédito original queda en estado 'restructured' (inmutable)
 *
 * Ejemplo de uso:
 * ```php
 * $result = $action->execute(new RestructureWithCustomPlanData(
 *     creditId: 123,
 *     installments: [
 *         new CustomInstallmentData(1, 170000, '2024-02-01'),
 *         new CustomInstallmentData(2, 170000, '2024-03-01'),
 *         // ...
 *         new CustomInstallmentData(7, 180000, '2024-08-01'),
 *     ],
 *     reason: 'Ajuste de cuotas a capacidad de pago',
 * ));
 * ```
 */
class RestructureCreditWithCustomInstallmentsAction
{
    public function __construct(
        private readonly CreditManager $creditManager,
        private readonly IrregularInstallmentGenerator $installmentGenerator,
        private readonly CustomInstallmentPlanValidator $validator,
    ) {}

    /**
     * Ejecuta la reestructuración con plan personalizado.
     *
     * @throws Exception Si la validación falla o hay error en la transacción
     */
    public function execute(RestructureWithCustomPlanData $data): CreditOperationResult
    {
        return DB::transaction(function () use ($data) {
            $user = Auth::user();

            if (! $user) {
                throw new Exception('Usuario no autenticado.');
            }

            // 1. Obtener y bloquear crédito original
            $credit = Credit::query()
                ->where('id', $data->creditId)
                ->where('company_id', $user->company_id)
                ->lockForUpdate()
                ->firstOrFail();

            // 2. Validar que se puede reestructurar
            $this->assertCanRestructure($credit);

            // 3. Calcular saldo pendiente (como Money)
            $remainingBalance = $this->creditManager->getRemainingBalanceAsMoney($credit);

            if ($remainingBalance->isZeroOrNegative()) {
                throw new Exception('El crédito no tiene saldo pendiente para reestructurar.');
            }

            // 4. Validar coherencia del plan de cuotas
            $this->validator->validate($data, $remainingBalance);

            // 5. Calcular capital pendiente (sin interés)
            $capitalPending = $this->calculatePendingPrincipal($credit);

            // 6. Crear snapshot de cuotas originales para auditoría
            $originalInstallmentsSnapshot = $this->createInstallmentsSnapshot($credit);

            // 7. Cerrar crédito original con estado 'restructured'
            $credit->update(['status' => Credit::STATUS_RESTRUCTURED]);

            // 8. Crear crédito hijo
            $newCredit = $this->createChildCredit($credit, $data, $capitalPending);

            // 9. Generar cuotas irregulares
            $installmentRows = $this->installmentGenerator->generate(
                credit: $newCredit,
                data: $data,
                totalPrincipal: $capitalPending,
            );

            $newCredit->installments()->createMany($installmentRows);

            // 10. Actualizar due_date del crédito con la última cuota
            $newCredit->update(['due_date' => $data->lastDueDate()]);

            // 11. Registrar logs de auditoría
            $restructureLog = $this->logRestructure(
                $credit,
                $newCredit,
                $data,
                $remainingBalance,
                $capitalPending,
                $originalInstallmentsSnapshot
            );

            $financialOperation = $this->logFinancialOperation(
                $credit,
                $newCredit,
                $data,
                $remainingBalance
            );

            $this->logAudit($credit, $newCredit, $data, $remainingBalance, $capitalPending);

            // 12. Refrescar estado del nuevo crédito
            $newCredit->refresh();
            $newCredit->refreshStatus();

            // 13. Retornar resultado estructurado
            return new CreditOperationResult(
                originalCredit: $credit->fresh(),
                newCredit: $newCredit->fresh(['installments']),
                restructureLog: $restructureLog,
                financialOperation: $financialOperation,
                previousBalance: $remainingBalance,
                newPrincipal: $capitalPending,
                cashOut: Money::zero(),
                compensatedAmount: $remainingBalance,
                operationType: 'restructure_custom_plan',
            );
        });
    }

    /**
     * Valida que el crédito puede ser reestructurado.
     */
    private function assertCanRestructure(Credit $credit): void
    {
        // No puede estar cerrado
        if ($credit->isClosed()) {
            throw new Exception(
                'No se puede reestructurar un crédito cerrado (estado: '.$credit->status.').'
            );
        }

        // No puede ser padre de otro crédito
        if ($credit->isParent()) {
            throw new Exception(
                'No se puede reestructurar un crédito que ya tiene hijos. '.
                'Debe reestructurar el crédito hijo activo.'
            );
        }
    }

    /**
     * Calcula el capital (principal) pendiente del crédito.
     */
    private function calculatePendingPrincipal(Credit $credit): Money
    {
        $credit->loadMissing('installments');

        $pendingPrincipal = $credit->installments->sum(function ($installment) {
            $principalPaid = min($installment->principal_amount, $installment->amount_paid);

            return max(0, $installment->principal_amount - $principalPaid);
        });

        return Money::of($pendingPrincipal);
    }

    /**
     * Crea snapshot de las cuotas originales para auditoría.
     */
    private function createInstallmentsSnapshot(Credit $credit): array
    {
        return $credit->installments->map(fn ($i) => [
            'number' => $i->installment_number,
            'due_date' => $i->due_date->toDateString(),
            'total_amount' => (float) $i->total_amount,
            'principal_amount' => (float) $i->principal_amount,
            'interest_amount' => (float) $i->interest_amount,
            'amount_paid' => (float) $i->amount_paid,
            'status' => $i->status,
        ])->toArray();
    }

    /**
     * Crea el crédito hijo con los datos de la reestructuración.
     */
    private function createChildCredit(
        Credit $original,
        RestructureWithCustomPlanData $data,
        Money $newPrincipal,
    ): Credit {
        return Credit::create([
            'company_id' => $original->company_id,
            'client_id' => $original->client_id,
            'collector_user_id' => $original->collector_user_id,
            'created_by_user_id' => Auth::id(),
            'parent_credit_id' => $original->id,

            'status' => Credit::STATUS_ACTIVE,
            'amount' => $newPrincipal->value(),
            'interest_rate' => $data->newInterestRate ?? $original->interest_rate,
            'installments_count' => $data->installmentsCount(),
            'periodicity' => $data->periodicity ?? $original->periodicity,

            'start_date' => now(),
            'first_due_date_override' => null,
            'due_day_1' => $original->due_day_1,
            'due_day_2' => $original->due_day_2,
            'due_date' => null, // Se actualiza después
        ]);
    }

    /**
     * Registra en credit_restructure_logs.
     */
    private function logRestructure(
        Credit $original,
        Credit $new,
        RestructureWithCustomPlanData $data,
        Money $previousBalance,
        Money $newPrincipal,
        array $originalSnapshot,
    ): CreditRestructureLog {
        return CreditRestructureLog::create([
            'original_credit_id' => $original->id,
            'new_credit_id' => $new->id,
            'company_id' => $original->company_id,
            'performed_by_user_id' => Auth::id(),
            'type' => CreditRestructureLog::TYPE_RESTRUCTURE,
            'previous_balance' => $previousBalance->value(),
            'new_principal' => $newPrincipal->value(),
            'new_interest_rate' => $data->newInterestRate ?? $original->interest_rate,
            'new_installment_count' => $data->installmentsCount(),
            'new_periodicity' => $data->periodicity ?? $original->periodicity,
            'additional_capital' => 0,
            'capitalized_interest' => $data->capitalizedInterest->value(),
            'reason' => $data->reason,
            'metadata' => [
                'operation' => 'restructure_custom_plan',
                'custom_installments' => array_map(fn ($i) => $i->toArray(), $data->installments),
                'original_installments_snapshot' => $originalSnapshot,
                'executed_at' => now()->toIso8601String(),
                'executed_by' => Auth::id(),
            ],
        ]);
    }

    /**
     * Registra en financial_operations.
     *
     * Para reestructuración:
     * - credit_amount = suma total del nuevo plan de cuotas
     * - compensated_amount = saldo del crédito original que se cancela
     * - cash_out = credit_amount - compensated_amount (= intereses capitalizados, si los hay)
     */
    private function logFinancialOperation(
        Credit $original,
        Credit $new,
        RestructureWithCustomPlanData $data,
        Money $previousBalance,
    ): FinancialOperation {
        // El monto del nuevo crédito es la suma de todas las cuotas del plan
        $creditAmount = $data->totalAmount();

        // Una reestructuración NO desembolsa efectivo (solo re-agenda el saldo);
        // el total del nuevo plan incluye interés futuro, que no es salida de caja.
        // cash_out = 0, consistente con RestructureCreditOperation y con el
        // resultado que esta misma acción retorna (cashOut: Money::zero()). (#96)
        $cashOut = Money::zero();

        return FinancialOperation::create([
            'company_id' => $original->company_id,
            'user_id' => Auth::id(),
            'operation_type' => 'credit_restructure',
            'source_credit_id' => $original->id,
            'target_credit_id' => $new->id,
            'credit_amount' => $creditAmount->value(),
            'compensated_amount' => $previousBalance->value(),
            'cash_out' => $cashOut->value(),
            'cash_in' => 0,
            'expense_id' => null,
            'notes' => sprintf(
                'Reestructuración con plan personalizado. Saldo anterior: %s. Nuevo plan: %d cuotas irregulares. Total: %s.',
                $previousBalance->formatWithCurrency(),
                $new->installments_count,
                $creditAmount->formatWithCurrency()
            ),
        ]);
    }

    /**
     * Registra en credit_audit_logs.
     */
    private function logAudit(
        Credit $original,
        Credit $new,
        RestructureWithCustomPlanData $data,
        Money $previousBalance,
        Money $newPrincipal,
    ): void {
        CreditAuditLog::create([
            'company_id' => $original->company_id,
            'credit_id' => $original->id,
            'user_id' => Auth::id(),
            'action' => CreditAuditEvent::CREDIT_RESTRUCTURED->value,
            'data' => [
                'operation_type' => 'restructure_custom_plan',
                'previous_balance' => $previousBalance->value(),
                'new_principal' => $newPrincipal->value(),
                'new_credit_id' => $new->id,
                'installments_count' => $data->installmentsCount(),
                'plan_total' => $data->totalAmount()->value(),
                'capitalized_interest' => $data->capitalizedInterest->value(),
                'new_interest_rate' => $data->newInterestRate,
                'reason' => $data->reason,
                'installments_summary' => array_map(fn ($i) => [
                    'n' => $i->number,
                    'amount' => $i->totalAmount,
                    'date' => $i->dueDate->toDateString(),
                ], $data->installments),
            ],
        ]);
    }
}
