<?php

namespace App\Services\Payments;

use App\Models\Credit;
use Illuminate\Support\Facades\DB;
use RuntimeException;

class PaymentInvariantGuard
{
    public function assertCreditIntegrity(Credit $credit): void
    {
        $creditId = $credit->id;
        $companyId = $credit->company_id;

        // A) Payments: applied_amount == sum(pivots) para no voided
        $badPayments = DB::table('payments as p')
            ->leftJoin('payment_installment as pi', 'pi.payment_id', '=', 'p.id')
            ->where('p.company_id', $companyId)
            ->where('p.credit_id', $creditId)
            ->where('p.voided', 0)
            ->groupBy('p.id', 'p.applied_amount')
            ->havingRaw('ROUND(p.applied_amount, 2) <> ROUND(COALESCE(SUM(pi.applied_amount),0), 2)')
            ->select('p.id')
            ->limit(5)
            ->pluck('id');

        if ($badPayments->isNotEmpty()) {
            throw new RuntimeException('Integridad rota: payment.applied_amount != SUM(pivot) en payments: '.$badPayments->implode(','));
        }

        // B) Installments: amount_paid == sum(pivots válidos) + forgiven_amount.
        //    El saldo condonado (forgiven_amount, p. ej. por pronto pago) cuenta como
        //    satisfecho aunque no tenga pivote, así que entra en el lado esperado.
        $badInstallments = DB::table('installments as i')
            ->leftJoin('payment_installment as pi', 'pi.installment_id', '=', 'i.id')
            ->leftJoin('payments as p', function ($join) {
                $join->on('p.id', '=', 'pi.payment_id')
                    ->where('p.voided', 0);
            })
            ->where('i.credit_id', $creditId)
            ->groupBy('i.id', 'i.amount_paid', 'i.forgiven_amount')
            ->havingRaw('ROUND(i.amount_paid, 2) <> ROUND(COALESCE(SUM(pi.applied_amount),0) + COALESCE(i.forgiven_amount, 0), 2)')
            ->select('i.id')
            ->limit(5)
            ->pluck('id');

        if ($badInstallments->isNotEmpty()) {
            throw new RuntimeException('Integridad rota: installment.amount_paid != SUM(pivot) en installments: '.$badInstallments->implode(','));
        }

        // C) Ningún pivote con payment voided (opcional, pero recomendable)
        $voidedPivots = DB::table('payment_installment as pi')
            ->join('payments as p', 'p.id', '=', 'pi.payment_id')
            ->where('p.company_id', $companyId)
            ->where('p.credit_id', $creditId)
            ->where('p.voided', 1)
            ->limit(1)
            ->exists();

        if ($voidedPivots) {
            throw new RuntimeException('Integridad rota: existen pivotes asociados a payments voided (deben eliminarse o excluirse del modelo).');
        }

        // D) Los pagos de reversión (monto negativo, compensatorios) NO deben tener
        //    pivotes: no aplican dinero a cuotas. Un pivote aquí lo sumarían
        //    materializeCredit/PaymentDistributor (filtran voided=0) re-inflando
        //    amount_paid → doble conteo del saldo.
        $reversalPivots = DB::table('payment_installment as pi')
            ->join('payments as p', 'p.id', '=', 'pi.payment_id')
            ->where('p.company_id', $companyId)
            ->where('p.credit_id', $creditId)
            ->where('p.payment_method', 'reversal')
            ->limit(1)
            ->exists();

        if ($reversalPivots) {
            throw new RuntimeException('Integridad rota: un pago de reversión no debe tener pivotes (causaría doble conteo del saldo).');
        }
    }
}
