<?php

declare(strict_types=1);

namespace App\Services\Pwa;

use App\Models\Credit;
use App\Models\Installment;
use App\Models\Payment;
use App\Models\User;
use App\ValueObjects\Money;
use Carbon\Carbon;

/**
 * Server-side validation service for PWA payments.
 *
 * All financial calculations MUST be validated here.
 * Frontend suggestions are for UX only - never trust client data.
 */
class PaymentValidationService
{
    /**
     * Validates a payment request and returns validation result.
     *
     * @return array{valid: bool, errors: array, credit?: Credit, calculated: array}
     */
    public function validate(
        int $creditId,
        float $amount,
        string $paymentDate,
        User $user,
        ?string $idempotencyKey = null
    ): array {
        $errors = [];
        $calculated = [];

        // 1. Verify credit exists and belongs to collector
        $credit = Credit::query()
            ->where('company_id', $user->company_id)
            ->where('collector_user_id', $user->id)
            ->with(['installments' => fn ($q) => $q->orderBy('installment_number')])
            ->find($creditId);

        if (! $credit) {
            return [
                'valid' => false,
                'errors' => ['credit_id' => ['Crédito no encontrado o no está asignado a ti.']],
                'calculated' => [],
            ];
        }

        // 2. Verify credit is in active status
        if (! in_array($credit->status, Credit::ACTIVE_STATUSES, true)) {
            return [
                'valid' => false,
                'errors' => ['credit_id' => ["El crédito está {$credit->getStatusLabel()} y no puede recibir pagos."]],
                'credit' => $credit,
                'calculated' => [],
            ];
        }

        // 3. Calculate REAL remaining balance from database (NEVER trust frontend)
        $realRemainingBalance = $this->calculateRealRemainingBalance($credit);
        $calculated['real_remaining_balance'] = $realRemainingBalance;

        // 4. Validate amount against real balance
        $paymentAmount = Money::of($amount);
        $maxAllowed = Money::of($realRemainingBalance)->add(Money::of(0.01)); // Tolerance for rounding

        if ($paymentAmount->value() > $maxAllowed->value()) {
            return [
                'valid' => false,
                'errors' => ['amount' => ["El monto excede el saldo pendiente de \${$realRemainingBalance}."]],
                'credit' => $credit,
                'calculated' => $calculated,
            ];
        }

        // 5. Validate minimum amount
        if ($paymentAmount->value() < 0.01) {
            return [
                'valid' => false,
                'errors' => ['amount' => ['El monto mínimo es $0.01.']],
                'credit' => $credit,
                'calculated' => $calculated,
            ];
        }

        // 6. Validate payment date is not in the future
        // IMPORTANTE: Comparar solo fechas (sin hora) para evitar edge cases de timezone
        // Un pago puede ser registrado para "hoy" incluso si son las 11:59pm
        $paymentDateObj = Carbon::parse($paymentDate)->startOfDay();
        $todayDate = Carbon::today(); // Midnight de hoy en timezone de la app

        if ($paymentDateObj->gt($todayDate)) {
            return [
                'valid' => false,
                'errors' => ['payment_date' => ['La fecha de pago no puede ser futura.']],
                'credit' => $credit,
                'calculated' => $calculated,
            ];
        }

        // 7. Check idempotency (duplicate prevention)
        if ($idempotencyKey) {
            $existingPayment = Payment::query()
                ->where('company_id', $user->company_id)
                ->where('idempotency_key', $idempotencyKey)
                ->first();

            if ($existingPayment) {
                return [
                    'valid' => false,
                    'errors' => ['idempotency_key' => ['Este pago ya fue registrado.']],
                    'credit' => $credit,
                    'calculated' => $calculated,
                    'duplicate' => true,
                    'existing_payment' => $existingPayment,
                ];
            }
        }

        // 8. Calculate payment distribution preview
        $distribution = $this->calculatePaymentDistribution($credit, $paymentAmount->value());
        $calculated['distribution'] = $distribution;
        $calculated['suggested_amount'] = $this->getSuggestedAmount($credit);

        return [
            'valid' => true,
            'errors' => [],
            'credit' => $credit,
            'calculated' => $calculated,
        ];
    }

    /**
     * Calculates the REAL remaining balance directly from installments.
     * This is the source of truth - never use cached/computed values for validation.
     */
    public function calculateRealRemainingBalance(Credit $credit): float
    {
        // Sum all unpaid amounts from installments (total_amount - amount_paid)
        $totalDue = $credit->installments()
            ->whereIn('status', [
                Installment::STATUS_PENDING,
                Installment::STATUS_PARTIAL_PAID,
                Installment::STATUS_OVERDUE,
            ])
            ->selectRaw('COALESCE(SUM(total_amount - amount_paid), 0) as total')
            ->value('total');

        return round((float) $totalDue, 2);
    }

    /**
     * Calculates how a payment would be distributed across installments.
     */
    public function calculatePaymentDistribution(Credit $credit, float $amount): array
    {
        $remaining = Money::of($amount);
        $distribution = [];

        // Get pending installments ordered by due date
        $installments = $credit->installments()
            ->whereIn('status', [
                Installment::STATUS_PENDING,
                Installment::STATUS_PARTIAL_PAID,
                Installment::STATUS_OVERDUE,
            ])
            ->orderBy('due_date')
            ->orderBy('installment_number')
            ->get();

        foreach ($installments as $installment) {
            if ($remaining->value() <= 0) {
                break;
            }

            $balanceDue = Money::of($installment->remaining_amount);
            $toApply = $remaining->value() >= $balanceDue->value()
                ? $balanceDue
                : $remaining;

            $distribution[] = [
                'installment_id' => $installment->id,
                'installment_number' => $installment->installment_number,
                'current_balance' => $balanceDue->value(),
                'amount_to_apply' => $toApply->value(),
                'new_balance' => $balanceDue->subtract($toApply)->value(),
                'will_complete' => $toApply->value() >= $balanceDue->value(),
            ];

            $remaining = $remaining->subtract($toApply);
        }

        return $distribution;
    }

    /**
     * Gets the suggested payment amount (next pending installment).
     */
    public function getSuggestedAmount(Credit $credit): float
    {
        $nextInstallment = $credit->installments()
            ->whereIn('status', [
                Installment::STATUS_PENDING,
                Installment::STATUS_PARTIAL_PAID,
                Installment::STATUS_OVERDUE,
            ])
            ->orderBy('due_date')
            ->orderBy('installment_number')
            ->first();

        return $nextInstallment ? round((float) $nextInstallment->remaining_amount, 2) : 0.0;
    }

    /**
     * Validates a batch of payments for sync.
     *
     * @return array{valid: array, invalid: array}
     */
    public function validateBatch(array $payments, User $user): array
    {
        $valid = [];
        $invalid = [];

        foreach ($payments as $payment) {
            $result = $this->validate(
                creditId: $payment['credit_id'],
                amount: (float) $payment['amount'],
                paymentDate: $payment['payment_date'],
                user: $user,
                idempotencyKey: $payment['idempotency_key'] ?? null
            );

            if ($result['valid']) {
                $valid[] = [
                    'payment' => $payment,
                    'credit' => $result['credit'],
                    'calculated' => $result['calculated'],
                ];
            } else {
                $invalid[] = [
                    'payment' => $payment,
                    'errors' => $result['errors'],
                    'duplicate' => $result['duplicate'] ?? false,
                    'existing_payment' => $result['existing_payment'] ?? null,
                ];
            }
        }

        return ['valid' => $valid, 'invalid' => $invalid];
    }
}
