<?php

declare(strict_types=1);

namespace App\Exceptions;

use Exception;
use Throwable;

/**
 * Excepción base para operaciones de crédito.
 * Incluye código de error estructurado para manejo en UI.
 */
class CreditOperationException extends Exception
{
    public const CODE_CREDIT_CLOSED = 'CREDIT_CLOSED';

    public const CODE_CREDIT_NOT_FOUND = 'CREDIT_NOT_FOUND';

    public const CODE_NO_BALANCE = 'NO_BALANCE';

    public const CODE_CAPITAL_PAID = 'CAPITAL_PAID';

    public const CODE_NO_PAYMENTS = 'NO_PAYMENTS';

    public const CODE_INSUFFICIENT_AMOUNT = 'INSUFFICIENT_AMOUNT';

    public const CODE_INVALID_STATE = 'INVALID_STATE';

    public const CODE_UNAUTHORIZED = 'UNAUTHORIZED';

    public const CODE_DUPLICATE_OPERATION = 'DUPLICATE_OPERATION';

    public const CODE_VALIDATION_FAILED = 'VALIDATION_FAILED';

    protected string $errorCode;

    protected array $context;

    public function __construct(
        string $message,
        string $errorCode = 'UNKNOWN',
        array $context = [],
        int $code = 0,
        ?Throwable $previous = null
    ) {
        parent::__construct($message, $code, $previous);
        $this->errorCode = $errorCode;
        $this->context = $context;
    }

    public function getErrorCode(): string
    {
        return $this->errorCode;
    }

    public function getContext(): array
    {
        return $this->context;
    }

    /**
     * Factory methods para excepciones comunes.
     */
    public static function creditClosed(int $creditId): self
    {
        return new self(
            "El crédito #{$creditId} está cerrado y no puede ser operado.",
            self::CODE_CREDIT_CLOSED,
            ['credit_id' => $creditId]
        );
    }

    public static function noRemainingBalance(int $creditId): self
    {
        return new self(
            "El crédito #{$creditId} no tiene saldo pendiente.",
            self::CODE_NO_BALANCE,
            ['credit_id' => $creditId]
        );
    }

    public static function capitalAlreadyPaid(int $creditId): self
    {
        return new self(
            "El crédito #{$creditId} tiene pagos de capital y está en modo SOLO LECTURA (POLÍTICA A PRO). ".
            'No se permite extender, refinanciar, reestructurar ni renovar.',
            self::CODE_CAPITAL_PAID,
            ['credit_id' => $creditId]
        );
    }

    public static function noPaymentsForRenewal(int $creditId): self
    {
        return new self(
            "El crédito #{$creditId} no tiene pagos realizados. ".
            'La renovación requiere que el cliente haya realizado al menos un pago.',
            self::CODE_NO_PAYMENTS,
            ['credit_id' => $creditId]
        );
    }

    public static function insufficientRenewalAmount(int $creditId, float $balance, float $requested): self
    {
        return new self(
            "El monto solicitado ({$requested}) es menor que el saldo pendiente ({$balance}). ".
            'La renovación requiere un monto igual o mayor al saldo.',
            self::CODE_INSUFFICIENT_AMOUNT,
            ['credit_id' => $creditId, 'balance' => $balance, 'requested' => $requested]
        );
    }

    public static function unauthorized(int $creditId): self
    {
        return new self(
            "No tiene autorización para operar sobre el crédito #{$creditId}.",
            self::CODE_UNAUTHORIZED,
            ['credit_id' => $creditId]
        );
    }

    public static function invalidState(int $creditId, string $currentState, array $allowedStates): self
    {
        return new self(
            "El crédito #{$creditId} está en estado '{$currentState}'. ".
            'Estados permitidos: '.implode(', ', $allowedStates),
            self::CODE_INVALID_STATE,
            ['credit_id' => $creditId, 'current_state' => $currentState, 'allowed_states' => $allowedStates]
        );
    }

    public static function duplicateOperation(int $creditId, string $operationType): self
    {
        return new self(
            "Ya existe una operación de {$operationType} en proceso para el crédito #{$creditId}.",
            self::CODE_DUPLICATE_OPERATION,
            ['credit_id' => $creditId, 'operation_type' => $operationType]
        );
    }
}
