<?php

declare(strict_types=1);

namespace App\Services\Audit;

use App\Support\Format;
use Carbon\Carbon;

/**
 * Servicio de presentación de solo lectura para datos de auditoría.
 *
 * Traduce claves técnicas a labels en español y formatea valores
 * para su presentación en UI. No define labels, iconos ni colores
 * de acciones (eso es responsabilidad de los enums).
 *
 * No recalcula, no infiere, no modifica datos.
 */
class AuditPresenter
{
    /**
     * Claves que se excluyen de la presentación por ser internas.
     */
    private const EXCLUDED_KEYS = [
        'id',
        'company_id',
        'created_at',
        'updated_at',
        'deleted_at',
        'user_id',
        'created_by_user_id',
        'pivot',
    ];

    /**
     * Diccionario de claves técnicas a labels en español.
     */
    private const FIELD_LABELS = [
        'amount' => 'Monto',
        'additional_capital' => 'Capital adicional',
        'interest_rate' => 'Tasa de interés',
        'new_interest_rate' => 'Tasa de interés',
        'total_interest' => 'Total intereses',
        'total_receivable' => 'Total a cobrar',
        'total_paid' => 'Total pagado',
        'balance' => 'Saldo pendiente',
        'installments' => 'Número de cuotas',
        'new_installments' => 'Número de cuotas',
        'num_installments' => 'Número de cuotas',
        'periodicity' => 'Periodicidad',
        'start_date' => 'Fecha de inicio',
        'payment_date' => 'Fecha de pago',
        'due_date' => 'Fecha de vencimiento',
        'status' => 'Estado',
        'applied_amount' => 'Monto aplicado',
        'extra_amount' => 'Monto excedente',
        'amount_paid' => 'Monto pagado',
        'new_credit_id' => 'Crédito generado',
        'from_credit_id' => 'Crédito origen',
        'reason' => 'Motivo',
        'collector_id' => 'Cobrador',
        'client_id' => 'Cliente',
        'executed_at' => 'Fecha de ejecución',
        'mode' => 'Modo',
        'notes' => 'Notas',
        'credit_id' => 'Crédito',
        'payment_id' => 'Pago',
        'installment_id' => 'Cuota',
        'amount_before' => 'Monto anterior',
        'amount_after' => 'Monto posterior',
        'amount_delta' => 'Diferencia de monto',
        'is_locked' => 'Bloqueado',
        'lock_reason' => 'Motivo de bloqueo',
        'approval_status' => 'Estado de aprobación',
        'due_day' => 'Día de cobro',
        'first_due_date' => 'Primera fecha de vencimiento',
    ];

    /**
     * Campos que contienen valores monetarios.
     */
    private const MONEY_FIELDS = [
        'amount',
        'additional_capital',
        'total_interest',
        'total_receivable',
        'total_paid',
        'balance',
        'applied_amount',
        'extra_amount',
        'amount_paid',
        'amount_before',
        'amount_after',
        'amount_delta',
    ];

    /**
     * Campos que contienen fechas.
     */
    private const DATE_FIELDS = [
        'start_date',
        'payment_date',
        'due_date',
        'first_due_date',
    ];

    /**
     * Traduce una clave técnica a su label en español.
     */
    public static function translateFieldKey(string $key): string
    {
        return self::FIELD_LABELS[$key] ?? ucfirst(str_replace('_', ' ', $key));
    }

    /**
     * Formatea un valor según el tipo de campo.
     *
     * No infiere ni recalcula. Solo formatea lo que recibe.
     */
    public static function translateFieldValue(string $key, mixed $value): string
    {
        if ($value === null) {
            return '—';
        }

        if (is_bool($value)) {
            return $value ? 'Sí' : 'No';
        }

        // Campos monetarios
        if (in_array($key, self::MONEY_FIELDS, true)) {
            return Format::money($value);
        }

        // Campos de fecha (formato Y-m-d)
        if (in_array($key, self::DATE_FIELDS, true)) {
            return self::formatDate($value);
        }

        // Campos datetime (terminan en _at)
        if (str_ends_with($key, '_at') && is_string($value)) {
            return self::formatDatetime($value);
        }

        // Estado
        if ($key === 'status') {
            return self::translateStatus($value);
        }

        // Periodicidad
        if ($key === 'periodicity') {
            return self::translatePeriodicity($value);
        }

        // Modo de pago
        if ($key === 'mode') {
            return self::translateMode($value);
        }

        // Aprobación
        if ($key === 'approval_status') {
            return self::translateApprovalStatus($value);
        }

        // Booleanos como string
        if ($key === 'is_locked') {
            return $value ? 'Sí' : 'No';
        }

        // Arrays y objetos: no formatear, dejar como están
        if (is_array($value)) {
            return json_encode($value, JSON_UNESCAPED_UNICODE) ?: '[]';
        }

        return (string) $value;
    }

    /**
     * Extrae campos que difieren entre dos snapshots.
     *
     * Retorna un array de diferencias con claves, labels y valores formateados.
     * No interpreta el significado del cambio.
     *
     * @return array<int, array{key: string, label: string, before: string|null, after: string|null}>
     */
    public static function computeChanges(?array $before, ?array $after): array
    {
        $changes = [];

        if ($before === null && $after === null) {
            return $changes;
        }

        // Creación: solo "después"
        if ($before === null && $after !== null) {
            foreach ($after as $key => $value) {
                if (self::isExcludedKey($key) || is_array($value)) {
                    continue;
                }

                $changes[] = [
                    'key' => $key,
                    'label' => self::translateFieldKey($key),
                    'before' => null,
                    'after' => self::translateFieldValue($key, $value),
                ];
            }

            return $changes;
        }

        // Eliminación: solo "antes" (aquí $before ya es no-null por las guardas previas)
        if ($after === null) {
            foreach ($before as $key => $value) {
                if (self::isExcludedKey($key) || is_array($value)) {
                    continue;
                }

                $changes[] = [
                    'key' => $key,
                    'label' => self::translateFieldKey($key),
                    'before' => self::translateFieldValue($key, $value),
                    'after' => null,
                ];
            }

            return $changes;
        }

        // Actualización: comparar campo por campo
        $allKeys = array_unique(array_merge(
            array_keys($before),
            array_keys($after),
        ));

        foreach ($allKeys as $key) {
            if (self::isExcludedKey($key)) {
                continue;
            }

            $beforeVal = $before[$key] ?? null;
            $afterVal = $after[$key] ?? null;

            // Saltar arrays anidados (se muestran en JSON)
            if (is_array($beforeVal) || is_array($afterVal)) {
                continue;
            }

            // Solo incluir si cambió
            if ((string) $beforeVal !== (string) $afterVal) {
                $changes[] = [
                    'key' => $key,
                    'label' => self::translateFieldKey($key),
                    'before' => self::translateFieldValue($key, $beforeVal),
                    'after' => self::translateFieldValue($key, $afterVal),
                ];
            }
        }

        return $changes;
    }

    /**
     * Convierte un bloque de datos crudo a pares [label => valor_formateado].
     *
     * Para CreditAuditLog: traduce las claves dentro de data.input
     * y campos de primer nivel relevantes.
     */
    public static function translateDataBlock(array $data): array
    {
        $result = [];

        // Si hay bloque 'input', traducir su contenido
        $input = $data['input'] ?? [];
        foreach ($input as $key => $value) {
            if (self::isExcludedKey($key) || is_array($value)) {
                continue;
            }

            $result[self::translateFieldKey($key)] = self::translateFieldValue($key, $value);
        }

        // Campos de primer nivel relevantes (excluyendo 'input' y claves internas)
        $topLevelKeys = ['new_credit_id', 'from_credit_id', 'executed_at'];
        foreach ($topLevelKeys as $key) {
            if (isset($data[$key]) && ! is_array($data[$key])) {
                $result[self::translateFieldKey($key)] = self::translateFieldValue($key, $data[$key]);
            }
        }

        return $result;
    }

    /**
     * Determina si una clave debe excluirse de la presentación.
     */
    private static function isExcludedKey(string $key): bool
    {
        return in_array($key, self::EXCLUDED_KEYS, true);
    }

    private static function formatDate(mixed $value): string
    {
        if (! is_string($value) || $value === '') {
            return (string) $value;
        }

        try {
            return Carbon::parse($value)->format('d/m/Y');
        } catch (\Exception) {
            return $value;
        }
    }

    private static function formatDatetime(mixed $value): string
    {
        if (! is_string($value) || $value === '') {
            return (string) $value;
        }

        try {
            return Carbon::parse($value)->format('d/m/Y H:i');
        } catch (\Exception) {
            return $value;
        }
    }

    private static function translateStatus(mixed $value): string
    {
        if (! is_string($value)) {
            return (string) $value;
        }

        return match ($value) {
            'active' => 'Activo',
            'paid' => 'Pagado',
            'overdue' => 'Vencido',
            'delayed' => 'Retrasado',
            'canceled' => 'Cancelado',
            'closed' => 'Cerrado',
            'pending' => 'Pendiente',
            'partial' => 'Parcial',
            'applied' => 'Aplicado',
            'voided' => 'Anulado',
            'archived' => 'Archivado',
            default => ucfirst($value),
        };
    }

    private static function translatePeriodicity(mixed $value): string
    {
        if (! is_string($value)) {
            return (string) $value;
        }

        return match ($value) {
            'daily' => 'Diario',
            'weekly' => 'Semanal',
            'biweekly' => 'Quincenal',
            'monthly' => 'Mensual',
            default => ucfirst($value),
        };
    }

    private static function translateMode(mixed $value): string
    {
        if (! is_string($value)) {
            return (string) $value;
        }

        return match ($value) {
            'credit_first' => 'A nivel de crédito',
            'installment_first' => 'A nivel de cuota',
            default => ucfirst($value),
        };
    }

    private static function translateApprovalStatus(mixed $value): string
    {
        if (! is_string($value)) {
            return (string) $value;
        }

        return match ($value) {
            'pending' => 'Pendiente',
            'approved' => 'Aprobado',
            'rejected' => 'Rechazado',
            default => ucfirst($value),
        };
    }
}
