<?php

declare(strict_types=1);

namespace App\Services\Credits;

use App\DTOs\ExtendCreditData;
use App\DTOs\ExtendWithInterestData;
use App\DTOs\RefinanceCreditData;
use App\DTOs\RenewCreditData;
use App\DTOs\RestructureCreditData;
use App\Exceptions\CreditOperationException;
use App\Models\Credit;
use App\Services\CreditManager;
use App\Support\CreditRules;
use App\ValueObjects\Money;
use Illuminate\Support\Facades\Auth;

/**
 * Validador centralizado de políticas de negocio para operaciones de crédito.
 *
 * Valida todas las reglas antes de ejecutar cualquier operación.
 * Separa completamente la validación de la ejecución.
 */
class CreditOperationValidator
{
    /**
     * Estados válidos para entrada en operaciones.
     */
    private const ALLOWED_STATES_FOR_OPERATIONS = [
        Credit::STATUS_ACTIVE,
        Credit::STATUS_DELAYED,
        Credit::STATUS_OVERDUE,
    ];

    public function __construct(
        private readonly CreditManager $creditManager,
    ) {}

    /**
     * Valida que el usuario pueda operar sobre el crédito (multi-tenant).
     */
    public function assertCanOperate(Credit $credit): void
    {
        $user = Auth::user();

        if (! $user) {
            throw CreditOperationException::unauthorized($credit->id);
        }

        // SuperAdmin puede ver pero NO operar
        if (method_exists($user, 'isSuperAdmin') && $user->isSuperAdmin()) {
            throw new CreditOperationException(
                'Los SuperAdmin no pueden ejecutar operaciones de crédito. Solo lectura.',
                CreditOperationException::CODE_UNAUTHORIZED,
                ['credit_id' => $credit->id, 'user_role' => 'super_admin']
            );
        }

        if ((int) $user->company_id !== (int) $credit->company_id) {
            throw CreditOperationException::unauthorized($credit->id);
        }
    }

    /**
     * Valida estado del crédito para operaciones.
     */
    public function assertValidState(Credit $credit): void
    {
        if ($credit->isClosed()) {
            throw CreditOperationException::creditClosed($credit->id);
        }

        if (! in_array($credit->status, self::ALLOWED_STATES_FOR_OPERATIONS, true)) {
            throw CreditOperationException::invalidState(
                $credit->id,
                $credit->status,
                self::ALLOWED_STATES_FOR_OPERATIONS
            );
        }
    }

    /**
     * Valida que el crédito NO sea padre (no tenga hijos derivados).
     *
     * Un crédito padre es uno que fue reestructurado/extendido/refinanciado
     * y tiene un crédito hijo activo. El padre debe permanecer inmutable.
     *
     * IMPORTANTE: El crédito a operar debe ser el HIJO activo (leaf credit),
     * no el padre cerrado.
     */
    public function assertNotParent(Credit $credit): void
    {
        if ($credit->isParent()) {
            throw new CreditOperationException(
                'No se puede operar sobre un crédito que ya tiene hijos derivados. '.
                'Debe operar sobre el crédito hijo activo.',
                CreditOperationException::CODE_VALIDATION_FAILED,
                [
                    'credit_id' => $credit->id,
                    'status' => $credit->status,
                    'has_children' => true,
                ]
            );
        }
    }

    /**
     * Valida que exista saldo pendiente.
     */
    public function assertHasBalance(Credit $credit): Money
    {
        $balance = $this->creditManager->getRemainingBalanceAsMoney($credit);

        if ($balance->lessThanOrEqual(0)) {
            throw CreditOperationException::noRemainingBalance($credit->id);
        }

        return $balance;
    }

    /**
     * POLÍTICA A PRO: Valida que no haya pagos de capital.
     */
    public function assertNoCapitalPaid(Credit $credit): void
    {
        if (CreditRules::hasCapitalPayments($credit)) {
            throw CreditOperationException::capitalAlreadyPaid($credit->id);
        }
    }

    /**
     * Valida requisitos específicos para EXTENSIÓN.
     *
     * - Crédito no cerrado
     * - Estado válido
     * - No es padre (tiene que ser leaf credit)
     * - Tiene saldo pendiente
     * - No tiene pagos de capital (POLÍTICA A PRO)
     */
    public function validateForExtension(Credit $credit, ExtendCreditData $data): Money
    {
        $this->assertCanOperate($credit);
        $this->assertValidState($credit);
        $this->assertNotParent($credit);
        $this->assertNoCapitalPaid($credit);

        $balance = $this->assertHasBalance($credit);

        if ($data->newInstallmentsCount <= 0) {
            throw new CreditOperationException(
                'El número de cuotas debe ser mayor a cero.',
                CreditOperationException::CODE_VALIDATION_FAILED,
                ['new_installments_count' => $data->newInstallmentsCount]
            );
        }

        return $balance;
    }

    /**
     * Valida requisitos específicos para REFINANCIACIÓN.
     *
     * - Crédito no cerrado
     * - Estado válido
     * - No es padre (tiene que ser leaf credit)
     * - Tiene saldo pendiente
     * - PERMITE créditos con capital pagado (#117): refinanciar es un préstamo
     *   nuevo (desembolsa capital + aplica tasa consentida), igual que la renovación.
     * - Capital adicional > 0 (validado en DTO)
     */
    public function validateForRefinance(Credit $credit, RefinanceCreditData $data): Money
    {
        $this->assertCanOperate($credit);
        $this->assertValidState($credit);
        $this->assertNotParent($credit);

        $balance = $this->assertHasBalance($credit);

        if ($data->newInstallmentsCount <= 0) {
            throw new CreditOperationException(
                'El número de cuotas debe ser mayor a cero.',
                CreditOperationException::CODE_VALIDATION_FAILED,
                ['new_installments_count' => $data->newInstallmentsCount]
            );
        }

        return $balance;
    }

    /**
     * Valida requisitos específicos para REESTRUCTURACIÓN.
     *
     * - Crédito no cerrado
     * - Estado válido (típicamente overdue/delayed)
     * - No es padre (tiene que ser leaf credit)
     * - Tiene saldo pendiente
     * - PERMITE créditos con capital pagado (#117): el usuario controla la tasa;
     *   si no la especifica, el hijo se crea a tasa 0 (redistribuye sin recargar interés).
     */
    public function validateForRestructure(Credit $credit, RestructureCreditData $data): Money
    {
        $this->assertCanOperate($credit);
        $this->assertValidState($credit);
        $this->assertNotParent($credit);

        $balance = $this->assertHasBalance($credit);

        if ($data->newInstallmentsCount <= 0) {
            throw new CreditOperationException(
                'El número de cuotas debe ser mayor a cero.',
                CreditOperationException::CODE_VALIDATION_FAILED,
                ['new_installments_count' => $data->newInstallmentsCount]
            );
        }

        // Validar que intereses capitalizados no excedan el saldo
        if ($data->capitalizedInterest->greaterThan($balance)) {
            throw new CreditOperationException(
                'Los intereses a capitalizar no pueden exceder el saldo pendiente.',
                CreditOperationException::CODE_VALIDATION_FAILED,
                [
                    'capitalized_interest' => $data->capitalizedInterest->value(),
                    'remaining_balance' => $balance->value(),
                ]
            );
        }

        return $balance;
    }

    /**
     * Valida requisitos específicos para RENOVACIÓN.
     *
     * - Crédito no cerrado
     * - Estado válido
     * - No es padre (tiene que ser leaf credit)
     * - DEBE tener pagos realizados (diferencia con otras operaciones)
     * - Nuevo monto >= saldo pendiente
     * - PERMITE créditos con capital pagado (#95): renovar es justamente el caso
     *   de un cliente que ya amortizó y quiere un préstamo nuevo. Igual que
     *   extendWithInterest, NO llama a assertNoCapitalPaid().
     */
    public function validateForRenewal(Credit $credit, RenewCreditData $data): Money
    {
        $this->assertCanOperate($credit);
        $this->assertValidState($credit);
        $this->assertNotParent($credit);

        // RENOVACIÓN REQUIERE PAGOS PREVIOS
        if (! CreditRules::hasPayments($credit)) {
            throw CreditOperationException::noPaymentsForRenewal($credit->id);
        }

        $balance = $this->assertHasBalance($credit);

        // Nuevo monto debe ser >= saldo pendiente
        if ($data->newCreditAmount->lessThan($balance)) {
            throw CreditOperationException::insufficientRenewalAmount(
                $credit->id,
                $balance->value(),
                $data->newCreditAmount->value()
            );
        }

        if ($data->newInstallmentsCount <= 0) {
            throw new CreditOperationException(
                'El número de cuotas debe ser mayor a cero.',
                CreditOperationException::CODE_VALIDATION_FAILED,
                ['new_installments_count' => $data->newInstallmentsCount]
            );
        }

        return $balance;
    }

    /**
     * Valida requisitos específicos para EXTENSIÓN CON INTERESES ADICIONALES.
     *
     * IMPORTANTE: Esta operación SÍ PERMITE créditos con pagos de capital.
     * Es la solución al problema de negocio donde clientes con cuotas pagadas
     * necesitan extender su crédito con nuevos intereses.
     *
     * Validaciones:
     * - Crédito no cerrado
     * - Estado válido (active, delayed, overdue)
     * - No es padre (debe ser leaf credit)
     * - Tiene saldo pendiente > 0
     * - NO valida hasCapitalPayments (esa es la diferencia clave)
     *
     * @param  Credit  $credit  Crédito a validar
     * @param  ExtendWithInterestData  $data  Datos de la operación
     * @return Money Saldo pendiente del crédito
     *
     * @throws CreditOperationException Si alguna validación falla
     */
    public function validateForExtensionWithInterest(Credit $credit, ExtendWithInterestData $data): Money
    {
        // 1. Validación multi-tenant
        $this->assertCanOperate($credit);

        // 2. Estado válido (no cerrado, en estados operables)
        $this->assertValidState($credit);

        // 3. No es un crédito padre (debe ser leaf credit)
        $this->assertNotParent($credit);

        // 4. NOTA: NO llamamos a assertNoCapitalPaid() porque esta operación
        // está específicamente diseñada para créditos CON pagos de capital.
        // Esta es la diferencia fundamental con validateForExtension().

        // 5. Verificar que hay saldo pendiente
        $balance = $this->assertHasBalance($credit);

        // 6. Validar número de cuotas
        if ($data->newInstallmentsCount <= 0) {
            throw new CreditOperationException(
                'El número de cuotas debe ser mayor a cero.',
                CreditOperationException::CODE_VALIDATION_FAILED,
                ['new_installments_count' => $data->newInstallmentsCount]
            );
        }

        // 7. Validar períodos de interés adicional (debe ser >= 0)
        if ($data->additionalInterestPeriods < 0) {
            throw new CreditOperationException(
                'Los períodos de interés adicional no pueden ser negativos.',
                CreditOperationException::CODE_VALIDATION_FAILED,
                ['additional_interest_periods' => $data->additionalInterestPeriods]
            );
        }

        // 8. Si se especifica nueva tasa, debe ser válida
        if ($data->newInterestRate !== null) {
            $rate = (float) $data->newInterestRate;
            if ($rate < 0 || $rate > 100) {
                throw new CreditOperationException(
                    'La tasa de interés debe estar entre 0% y 100%.',
                    CreditOperationException::CODE_VALIDATION_FAILED,
                    ['new_interest_rate' => $data->newInterestRate]
                );
            }
        }

        return $balance;
    }

    /**
     * Verifica si un crédito PUEDE ser extendido con intereses adicionales.
     *
     * Similar a las otras verificaciones pero sin restricción de capital pagado.
     * Útil para UI (mostrar/ocultar el botón de la acción).
     *
     * @param  Credit  $credit  Crédito a verificar
     * @return bool True si puede ser extendido con intereses
     */
    public function canExtendWithInterest(Credit $credit): bool
    {
        try {
            $this->assertCanOperate($credit);
            $this->assertValidState($credit);
            $this->assertNotParent($credit);
            // NO verificamos assertNoCapitalPaid - esa es la clave
            $this->assertHasBalance($credit);

            return true;
        } catch (CreditOperationException) {
            return false;
        }
    }
}
