<?php

namespace App\Services;

use App\Enums\CreditAuditEvent;
use App\Models\Company;
use App\Models\Credit;
use App\Models\CreditAuditLog;
use App\Models\FinancialOperation;
use App\Support\CreditRules;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;

class CreditOperationService
{
    /*=========================================================
    | ENTRY POINT — OPERACIONES CONTROLADAS (EXTENSIÓN / CLON)
    =========================================================*/
    public function executeOperation(Credit $credit, array $data): Credit
    {
        $operation = $data['operation_type'] ?? null;

        if (! $operation) {
            throw new Exception('Debe seleccionar un tipo de operación.');
        }

        // Multi-tenant seguro
        $this->assertCanOperateOnCredit($credit);

        // Nueva política mejorada:
        // Solo prohibimos si pagó capital
        CreditRules::ensureNoCapitalPaid($credit);

        return match ($operation) {
            'extension' => $this->extendCredit($credit, $data),
            'clone' => $this->cloneAsNewEditableCredit($credit, $data),
            default => throw new Exception("Operación '{$operation}' no soportada."),
        };
    }

    /*=========================================================
    | CREACIÓN DE CRÉDITO NUEVO
    =========================================================*/
    public function createNewCredit(array $data): Credit
    {
        return DB::transaction(function () use ($data) {

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

            $companyId = $data['company_id'] ?? $user->company_id;

            if (! (method_exists($user, 'isSuperAdmin') && $user->isSuperAdmin())) {
                $companyId = $user->company_id;
            }

            $clientId = $data['client_id'] ?? null;

            if (! $clientId) {
                throw new Exception('El campo client_id es obligatorio para crear un crédito.');
            }

            $credit = Credit::create([
                'company_id' => $companyId,
                'client_id' => $clientId,
                'collector_user_id' => $data['collector_user_id'] ?? null,
                'created_by_user_id' => $user->id,

                'status' => Credit::STATUS_ACTIVE,
                'amount' => $data['amount'],
                'interest_rate' => $data['interest_rate'],
                'installments_count' => $data['installments_count'],
                'periodicity' => $data['periodicity'],

                'start_date' => $data['start_date'] ?? now(),
                'first_due_date_override' => $data['first_due_date_override'] ?? null,
                'due_date' => $data['due_date'] ?? null,
                'due_day_1' => $data['due_day_1'] ?? null,
                'due_day_2' => $data['due_day_2'] ?? null,
                'idempotency_key' => $data['idempotency_key'] ?? null,
            ]);

            app(CreditCalculator::class)->generateInstallments($credit);

            $expense = app(FinanceAutoLogger::class)->logExpense([
                'company_id' => $credit->company_id,
                'user_id' => $user->id,
                'collector_user_id' => $credit->collector_user_id,
                'amount' => $credit->amount,
                'credit_id' => $credit->id,
                'category' => 'disbursement',
                'operation_date' => $credit->start_date,
                'notes' => 'Desembolso automático del crédito',
                'metadata' => ['source' => 'credit_created'],
            ]);

            FinancialOperation::create([
                'company_id' => $credit->company_id,
                'user_id' => $user->id,
                'operation_type' => 'credit_disbursement',
                'target_credit_id' => $credit->id,
                'credit_amount' => $credit->amount,
                'compensated_amount' => 0,
                'cash_out' => $credit->amount,
                'cash_in' => 0,
                'expense_id' => $expense->id,
                'notes' => 'Desembolso inicial del crédito',
            ]);

            $this->audit(
                creditId: $credit->id,
                action: CreditAuditEvent::CREDIT_CREATED,
                data: $data,
            );

            return $credit;
        });
    }

    /**
     * EXTENDER CRÉDITO
     *
     * Reglas contables:
     * - NO se elimina el Expense original (desembolso real).
     * - NO se crea un nuevo Expense (una extensión NO es un desembolso).
     * - Sólo se genera un nuevo crédito hijo con el saldo pendiente.
     * - El crédito original queda cancelado.
     * - Caja NO se mueve.
     */
    protected function extendCredit(Credit $credit, array $data): Credit
    {
        return DB::transaction(function () use ($credit, $data) {

            // 1) Validar políticas de negocio
            CreditRules::ensureNoCapitalPaid($credit);
            $this->assertCanOperateOnCredit($credit);

            // 2) Lock para evitar condiciones de carrera
            $credit = Credit::where('id', $credit->id)
                ->where('company_id', $credit->company_id)
                ->lockForUpdate()
                ->firstOrFail();

            if ($credit->isClosed()) {
                throw new Exception('No se puede extender un crédito cerrado.');
            }

            // 3) Obtener saldo pendiente REAL
            $remaining = app(CreditManager::class)
                ->getRemainingBalance($credit);

            if ($remaining <= 0) {
                throw new Exception('El crédito no tiene saldo pendiente para extenderse.');
            }

            // 4) Leer nuevas cuotas
            $newInstallments = (int) ($data['new_installments'] ?? $credit->installments_count);
            if ($newInstallments <= 0) {
                throw new Exception('El número de cuotas debe ser mayor a cero.');
            }

            // 5) Fecha contable del nuevo crédito
            $startDate = isset($data['start_date'])
                ? Carbon::parse($data['start_date'])
                : now();

            // 6) CANCELAR crédito original (no toca caja)
            $credit->update(['status' => Credit::STATUS_CANCELED]);

            // 7) Crear NUEVO crédito hijo con saldo pendiente
            $new = Credit::create([
                'company_id' => $credit->company_id,
                'client_id' => $credit->client_id,
                'collector_user_id' => $credit->collector_user_id,
                'created_by_user_id' => Auth::id(),
                'parent_credit_id' => $credit->id,

                'status' => Credit::STATUS_ACTIVE,
                'amount' => $remaining,
                'interest_rate' => $credit->interest_rate,
                'installments_count' => $newInstallments,
                'periodicity' => $data['periodicity'] ?? $credit->periodicity,

                'start_date' => $startDate,
                'first_due_date_override' => $data['first_due_date_override'] ?? null,
                'due_day_1' => $data['due_day_1'] ?? $credit->due_day_1,
                'due_day_2' => $data['due_day_2'] ?? $credit->due_day_2,
                'due_date' => null,
            ]);

            // 8) Generar nuevas cuotas del crédito hijo
            app(CreditCalculator::class)->generateInstallments($new);

            FinancialOperation::create([
                'company_id' => $credit->company_id,
                'user_id' => Auth::id(),
                'operation_type' => 'credit_extension',
                'source_credit_id' => $credit->id,
                'target_credit_id' => $new->id,
                'credit_amount' => $remaining,
                'compensated_amount' => $remaining,
                'cash_out' => 0,
                'cash_in' => 0,
                'notes' => 'Extensión del crédito sin movimiento de caja',
            ]);

            // 9) Auditoría contable
            $this->audit(
                creditId: $credit->id,
                action: CreditAuditEvent::CREDIT_EXTENDED,
                data: $data,
                newCreditId: $new->id
            );

            return $new;
        });
    }

    /*=========================================================
    | CLONAR EDITABLE
    =========================================================*/
    protected function cloneAsNewEditableCredit(Credit $credit, array $data): Credit
    {
        return DB::transaction(function () use ($credit, $data) {

            CreditRules::ensureNoCapitalPaid($credit);

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

            $remaining = app(CreditManager::class)->getRemainingBalance($credit);

            if ($remaining <= 0) {
                throw new Exception('El crédito no tiene saldo pendiente para generar uno nuevo.');
            }

            $newInstallments = (int) ($data['new_installments'] ?? $credit->installments_count);
            if ($newInstallments <= 0) {
                throw new Exception('El número de cuotas debe ser mayor a cero.');
            }

            $newAmount = (float) ($data['amount'] ?? $remaining);

            $startDate = isset($data['start_date'])
                ? Carbon::parse($data['start_date'])
                : now();

            // Un clon "editable" es un crédito NUEVO con desembolso real: NO cierra al
            // padre (a diferencia de las transformaciones), así que suma +1 al conteo de
            // créditos activos y saca caja. Debe respetar el cupo del plan igual que
            // CreateCredit (créditos activos + volumen mensual); antes se colaba sin
            // control vía "Duplicar" y podía superar max_active_credits/max_monthly_volume.
            // Lanza PlanLimitExceededException (la capturan las acciones "Duplicar" y
            // notifican); dentro de la transacción → si excede, no se crea nada.
            app(PlanLimitService::class)->assertCanCreateCredit(Company::findOrFail($credit->company_id), (float) $newAmount);

            $new = Credit::create([
                'company_id' => $credit->company_id,
                'client_id' => $credit->client_id,
                'collector_user_id' => $credit->collector_user_id,
                'created_by_user_id' => Auth::id(),
                'parent_credit_id' => $credit->id,

                'status' => Credit::STATUS_ACTIVE,
                'amount' => $newAmount,
                'interest_rate' => $data['interest_rate'] ?? $credit->interest_rate,
                'installments_count' => $newInstallments,
                'periodicity' => $data['periodicity'] ?? $credit->periodicity,

                'start_date' => $startDate,
                'first_due_date_override' => $data['first_due_date_override'] ?? null,
                'due_day_1' => $data['due_day_1'] ?? $credit->due_day_1,
                'due_day_2' => $data['due_day_2'] ?? $credit->due_day_2,
                'due_date' => null,
            ]);

            app(CreditCalculator::class)->generateInstallments($new);

            // PATCH — recalcular el estado real del nuevo crédito
            $new->refreshStatus();

            $expense = app(FinanceAutoLogger::class)->logExpense([
                'company_id' => $new->company_id,
                'user_id' => Auth::id(),
                'collector_user_id' => $new->collector_user_id,
                'amount' => $new->amount,
                'credit_id' => $new->id,
                'category' => 'disbursement',
                'operation_date' => $new->start_date,
                'notes' => 'Desembolso automático del crédito (clon editable)',
                'metadata' => [
                    'source' => 'credit_cloned_editable',
                    'original_credit_id' => $credit->id,
                ],
            ]);

            FinancialOperation::create([
                'company_id' => $new->company_id,
                'user_id' => Auth::id(),
                'operation_type' => 'credit_disbursement',
                'source_credit_id' => $credit->id,
                'target_credit_id' => $new->id,
                'credit_amount' => $new->amount,
                'compensated_amount' => 0,
                'cash_out' => $new->amount,
                'cash_in' => 0,
                'expense_id' => $expense->id,
                'notes' => 'Clon editable con nuevo desembolso',
            ]);

            $this->audit(
                creditId: $credit->id,
                action: CreditAuditEvent::CREDIT_CLONED_EDITABLE,
                data: $data,
                newCreditId: $new->id
            );

            return $new;
        });
    }

    /*=========================================================
    | CANCELAR
    =========================================================*/
    public function cancelCredit(Credit $credit, ?string $reason = null): Credit
    {
        return DB::transaction(function () use ($credit, $reason) {

            $this->assertCanOperateOnCredit($credit);

            CreditRules::ensureNoCapitalPaid($credit);

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

            $credit->update(['status' => Credit::STATUS_CANCELED]);

            app(FinanceAutoLogger::class)->deleteCreditExpense($credit->id, $credit->company_id);

            $this->audit(
                creditId: $credit->id,
                action: CreditAuditEvent::CREDIT_CANCELED,
                data: ['reason' => $reason],
            );

            return $credit;
        });
    }

    /*=========================================================
    | AUDITORÍA
    =========================================================*/
    public function audit(
        int $creditId,
        string|CreditAuditEvent $action,
        array $data = [],
        ?int $newCreditId = null
    ): void {
        $actionValue = $action instanceof CreditAuditEvent ? $action->value : $action;

        $credit = Credit::select('company_id')->find($creditId);
        $user = Auth::user();

        CreditAuditLog::create([
            'company_id' => $credit?->company_id ?? $user->company_id,
            'credit_id' => $creditId,
            'user_id' => $user?->id,
            'action' => $actionValue,
            'data' => [
                'input' => $data,
                'new_credit_id' => $newCreditId,
            ],
        ]);
    }

    /*=========================================================
    | MULTITENANT
    =========================================================*/
    protected function assertCanOperateOnCredit(Credit $credit): void
    {
        $user = Auth::user();
        if (! $user) {
            throw new Exception('Usuario no autenticado.');
        }

        if (method_exists($user, 'isSuperAdmin') && $user->isSuperAdmin()) {
            return;
        }

        if ((int) $user->company_id !== (int) $credit->company_id) {
            throw new Exception('No está autorizado para operar sobre este crédito.');
        }
    }
}
