<?php

namespace App\Services;

use App\Models\ConfiguracionEmpresa;
use App\Models\Impresora;
use App\Models\OrdenLavanderia;
use App\Models\OrdenLavanderiaDetalle;
use App\Models\User;
use App\Support\ImpresoraResolver;
use App\Support\LaundryBagLabel;
use App\Support\LaundrySemaphoreColorResolver;
use App\Support\TicketThermalSections;
use App\Support\ThermalLineWidth;
use App\Support\TicketObservationLines;
use Illuminate\Support\Carbon;

class TicketService
{
    public function __construct(
        private readonly ImpresionService $impresionService
    ) {}

    public function normalizarTexto(?string $texto): string
    {
        $texto = (string) ($texto ?? '');
        if ($texto === '') {
            return '';
        }

        $map = [
            'á' => 'a', 'é' => 'e', 'í' => 'i', 'ó' => 'o', 'ú' => 'u',
            'Á' => 'A', 'É' => 'E', 'Í' => 'I', 'Ó' => 'O', 'Ú' => 'U',
            'ñ' => 'n', 'Ñ' => 'N', 'ü' => 'u', 'Ü' => 'U',
        ];

        return strtr($texto, $map);
    }

    public function generarTicketVenta(OrdenLavanderia $orden, ?int $anchoLinea = null): string
    {
        $ancho = max(32, min(64, $anchoLinea ?? ThermalLineWidth::DEFAULT_CHARS_80MM));
        $config = ConfiguracionEmpresa::query()->where('activo', true)->first();

        $empresa = $this->normalizarTexto(
            $config?->ticket_encabezado_nombre
            ?? $config?->nombre_empresa
            ?? config('app.name', 'Lavanderia')
        );
        $rfc = $this->normalizarTexto($config?->ticket_encabezado_rfc ?? $config?->rfc ?? '');
        $domicilio = $this->normalizarTexto(
            $config?->ticket_encabezado_domicilio
            ?? trim(($config?->direccion ?? '').' '.($config?->codigo_postal ?? '').' '.($config?->ciudad ?? ''))
        );

        $lineas = [];
        $lineas[] = str_repeat('=', $ancho);
        $lineas[] = $empresa;
        if ($rfc !== '') {
            $lineas[] = 'RFC: '.$rfc;
        }
        if (trim($domicilio) !== '') {
            foreach ($this->partirLineas($domicilio, $ancho) as $dl) {
                $lineas[] = $dl;
            }
        }
        $lineas[] = str_repeat('=', $ancho);
        $lineas[] = 'TICKET #'.($orden->folio_unico ?: $orden->id);
        $fecha = $orden->created_at instanceof Carbon
            ? $orden->created_at->format('d/m/Y H:i')
            : now()->format('d/m/Y H:i');
        $lineas[] = 'Fecha: '.$fecha;

        $vendedor = $orden->relationLoaded('recepcionista')
            ? ($orden->recepcionista?->name ?? '')
            : '';
        if ($vendedor !== '') {
            $lineas[] = 'Atendio: '.$this->normalizarTexto($vendedor);
        }

        $cliente = $this->normalizarTexto($orden->cliente?->nombre ?? $orden->cliente_nombre ?? 'Mostrador');
        $lineas[] = 'Cliente: '.$cliente;
        $lineas[] = str_repeat('-', $ancho);

        $orden->loadMissing([
            'detalles.producto',
            'detalles.laundrySaleProduct',
            'detalles.garmentType',
            'detalles.etapa',
            'detalles.loadType',
            'pagos',
        ]);

        foreach ($orden->detalles as $detalle) {
            if ($detalle->is_informational) {
                continue;
            }
            foreach ($this->lineasDetalle($detalle, $ancho) as $lineaDetalle) {
                $lineas[] = $lineaDetalle;
            }
        }

        $lineas[] = str_repeat('-', $ancho);

        $total = (float) ($orden->total ?? 0);
        $lineas[] = $this->lineaImporte('TOTAL:', $total, $ancho);

        $pagos = $orden->pagos;
        if ($pagos->isNotEmpty()) {
            $lineas[] = 'FORMAS DE PAGO:';
            foreach ($pagos as $pago) {
                $metodo = $this->etiquetaMetodoPago((string) $pago->metodo_pago);
                $lineas[] = '  - '.$metodo.': $'.number_format((float) $pago->monto, 2, '.', ',');
            }
        } elseif ($orden->metodo_pago && $total > 0) {
            $lineas[] = '  - '.$this->etiquetaMetodoPago((string) $orden->metodo_pago).': $'.number_format($total, 2, '.', ',');
        }

        if ($this->debeMostrarObservaciones($orden)) {
            $lineas = array_merge($lineas, $this->lineasObservacionesTicket($orden, $ancho));
        }

        $entrega = LaundrySemaphoreColorResolver::promisedAt($orden);
        $lineas[] = str_repeat('-', $ancho);
        $lineas[] = 'SE ENTREGA: '.$this->normalizarTexto(LaundrySemaphoreColorResolver::formatEntregaTicket($entrega));

        $folioBarcode = \App\Support\LaundryScanCode::barcodePayload((string) ($orden->folio_unico ?? ''));
        if ($folioBarcode !== '') {
            $lineas[] = str_repeat('-', $ancho);
            $lineas[] = '@@CENTER:Escanee para operar';
            $lineas[] = '@@BARCODE:'.$folioBarcode;
            $lineas[] = '@@CENTER:'.$folioBarcode;
        }

        $lineas[] = str_repeat('=', $ancho);
        foreach ($this->lineasPieTicket($orden, $config, $ancho) as $pieLinea) {
            $lineas[] = $pieLinea;
        }
        $lineas[] = '';

        return implode("\n", $lineas);
    }

    public function generarEtiquetaBolsa(
        OrdenLavanderia $orden,
        ?int $anchoLinea = null,
        ?int $indiceBolsa = null,
        ?int $totalBolsas = null
    ): string {
        return LaundryBagLabel::contenidoTermico(
            $orden,
            $anchoLinea,
            $indiceBolsa,
            $totalBolsas
        );
    }

    /**
     * Pie del ticket térmico desde configuracion_empresa.ticket_footer_text.
     *
     * @return list<string>
     */
    private function lineasPieTicket(OrdenLavanderia $orden, ?ConfiguracionEmpresa $config, int $ancho): array
    {
        $rawFooter = trim((string) ($config?->ticket_footer_text ?? ''));
        if ($rawFooter === '') {
            return ['GRACIAS POR SU COMPRA!'];
        }

        $replaceVars = $this->variablesTicket($orden);
        $lineas = [];
        foreach (preg_split("/\r\n|\n|\r/", $rawFooter) ?: [] as $line) {
            $line = trim(strtr((string) $line, $replaceVars));
            if ($line === '') {
                continue;
            }
            foreach ($this->partirLineas($this->normalizarTexto($line), $ancho) as $wrapped) {
                $lineas[] = $wrapped;
            }
        }

        return $lineas !== [] ? $lineas : ['GRACIAS POR SU COMPRA!'];
    }

    /**
     * @return array<string, string>
     */
    private function variablesTicket(OrdenLavanderia $orden): array
    {
        $fecha = $orden->created_at instanceof Carbon ? $orden->created_at : now();
        $entrega = LaundrySemaphoreColorResolver::promisedAt($orden);
        if (! $entrega instanceof Carbon) {
            $entrega = Carbon::parse($entrega, config('app.timezone'));
        }
        $entregaFmt = LaundrySemaphoreColorResolver::formatEntregaTicket($entrega);

        return [
            '{folio}' => (string) ($orden->folio_unico ?? ''),
            '{fecha_hora}' => $fecha->format('d/m/Y H:i:s'),
            '{fecha}' => $fecha->format('d/m/Y'),
            '{cliente}' => $this->normalizarTexto($orden->cliente?->nombre ?? $orden->cliente_nombre ?? ''),
            '{total}' => number_format((float) ($orden->total ?? 0), 2, '.', ','),
            '{fecha_entrega}' => $entrega->format('d/m/Y'),
            '{fecha_entrega_hora}' => $entrega->format('H:i'),
            '{fecha_entrega_dia}' => strtoupper($entrega->locale('es')->isoFormat('ddd')),
            '{fecha_entrega_completa}' => $entregaFmt,
            '{recepcionista}' => $this->normalizarTexto($orden->recepcionista?->name ?? 'N/D'),
        ];
    }

    /**
     * @return list<string>
     */
    private function lineasObservacionesTicket(OrdenLavanderia $orden, int $ancho): array
    {
        $lineas = [];
        $payload = TicketObservationLines::forOrder($orden, $orden->detalles);

        if (! TicketObservationLines::hasAny($payload)) {
            $obs = [];
            if (trim((string) ($orden->instrucciones ?? '')) !== '') {
                $obs[] = trim((string) $orden->instrucciones);
            }
            $ropa = trim((string) ($orden->detalles_ropa ?? ''));
            if ($ropa !== '' && ! preg_match('/^ver detalle/i', $ropa) && ! TicketObservationLines::isPlaceholderDetallesRopa($ropa)) {
                $obs[] = $ropa;
            }
            if ($obs === []) {
                return [];
            }

            $lineas[] = str_repeat('-', $ancho);
            $lineas[] = 'OBSERVACIONES:';
            foreach ($obs as $bloque) {
                foreach ($this->partirLineas($this->normalizarTexto($bloque), $ancho - 2) as $ol) {
                    $lineas[] = '  '.$ol;
                }
            }

            return $lineas;
        }

        if ($payload['garment_sections'] !== []) {
            $lineas[] = str_repeat('-', $ancho);
            $lineas[] = TicketObservationLines::TITLE_OBSERVACIONES_POR_PRENDA;
            foreach ($payload['garment_sections'] as $section) {
                $lineas[] = ' '.$this->normalizarTexto($section['title']).':';
                foreach ($section['lines'] as $obsLine) {
                    foreach ($this->partirLineas($this->normalizarTexto($obsLine), $ancho - 2) as $ol) {
                        $lineas[] = '  '.$ol;
                    }
                }
            }
        }

        foreach ($payload['general_lines'] as $general) {
            $lineas[] = str_repeat('-', $ancho);
            $lineas[] = strtoupper($this->normalizarTexto($general['label'])).':';
            foreach ($this->partirLineas($this->normalizarTexto($general['text']), $ancho - 2) as $ol) {
                $lineas[] = '  '.$ol;
            }
        }

        return $lineas;
    }

    /**
     * @return array{success: bool, message?: string, error?: string}
     */
    public function imprimirTicket(
        OrdenLavanderia $orden,
        ?int $impresoraId = null,
        ?User $user = null,
        bool $posReception = false
    ): array {
        $orden->refresh();
        $orden->loadMissing([
            'recepcionista',
            'cliente',
            'detalles.producto',
            'detalles.laundrySaleProduct',
            'detalles.garmentType',
            'detalles.etapa',
            'detalles.loadType',
            'pagos',
        ]);

        $impresora = $posReception
            ? ImpresoraResolver::forPosReceptionTicket($impresoraId)
            : ImpresoraResolver::forVentaTicket($user, $impresoraId);

        if ($impresora === null) {
            $areaHint = $posReception
                ? 'el área de recepción'
                : ($user?->area_id !== null
                    ? 'su área asignada'
                    : 'el área de respaldo ('.ImpresoraResolver::fallbackTicketAreaCode().')');

            return [
                'success' => false,
                'error' => 'No hay impresora activa de venta configurada para '.$areaHint,
            ];
        }

        $contenido = $this->generarTicketVenta(
            $orden,
            ThermalLineWidth::charsFromImpresora($impresora)
        );

        return $this->impresionService->imprimirEscPosLocal(
            $impresora->nombre_sistema,
            $contenido,
            $impresora->tipo_ticket,
            $impresora
        );
    }

    /**
     * @return array{success: bool, message?: string, error?: string}
     */
    public function imprimirEtiquetaBolsa(
        OrdenLavanderia $orden,
        ?int $indice = null,
        ?int $impresoraId = null,
        bool $posReception = false
    ): array {
        $orden->loadMissing(['bolsas', 'cliente', 'slaTipoServicio']);

        $bolsas = $orden->bolsas->sortBy('indice')->values();
        if ($bolsas->isEmpty()) {
            return [
                'success' => false,
                'error' => 'No hay bolsas. Indique la cantidad y guarde primero.',
            ];
        }

        if ($indice !== null) {
            $bolsa = $bolsas->first(fn ($b) => (int) $b->indice === $indice);
            if ($bolsa === null) {
                return [
                    'success' => false,
                    'error' => 'No existe la bolsa indicada para esta orden.',
                ];
            }
            $bolsas = collect([$bolsa]);
        }

        $impresora = $posReception
            ? ImpresoraResolver::forPosReceptionTicket($impresoraId)
            : ImpresoraResolver::forVentaTicket(null, $impresoraId);

        if ($impresora === null) {
            return [
                'success' => false,
                'error' => 'No hay impresora activa de venta configurada para el área de recepción',
            ];
        }

        $ancho = ThermalLineWidth::charsFromImpresora($impresora);
        $totalBolsas = (int) $orden->bolsas->count();
        $ultimoResultado = null;

        foreach ($bolsas as $bolsa) {
            $contenido = $this->generarEtiquetaBolsa(
                $orden,
                $ancho,
                (int) $bolsa->indice,
                $totalBolsas
            );
            $ultimoResultado = $this->impresionService->imprimirEscPosLocal(
                $impresora->nombre_sistema,
                $contenido,
                $impresora->tipo_ticket,
                $impresora
            );

            if (! ($ultimoResultado['success'] ?? false)) {
                return [
                    'success' => false,
                    'error' => $ultimoResultado['error'] ?? 'No se pudo imprimir la etiqueta.',
                ];
            }
        }

        return [
            'success' => true,
            'message' => $bolsas->count() > 1
                ? "Se enviaron {$bolsas->count()} etiquetas a la impresora de recepción."
                : ($ultimoResultado['message'] ?? 'Etiqueta enviada a la impresora de recepción.'),
        ];
    }

    /** @deprecated Reservado para compatibilidad interna. */
    private function obtenerImpresoraPorDefecto(): ?Impresora
    {
        return ImpresoraResolver::forVentaTicket(null, null);
    }

    private function debeMostrarObservaciones(OrdenLavanderia $orden): bool
    {
        if (array_key_exists('mostrar_observaciones_en_ticket', $orden->getAttributes())
            || $orden->mostrar_observaciones_en_ticket !== null) {
            return (bool) $orden->mostrar_observaciones_en_ticket;
        }

        return true;
    }

    /**
     * @return list<string>
     */
    private function lineasDetalle(OrdenLavanderiaDetalle $detalle, int $ancho): array
    {
        $nombre = $this->normalizarTexto(TicketThermalSections::lineConcepto($detalle));
        $nombreMax = max(12, $ancho - 14);
        $nombre = mb_substr($nombre, 0, $nombreMax);

        $cant = (float) $detalle->cantidad;
        if (($detalle->billing_mode ?? '') === OrdenLavanderiaDetalle::BILLING_BY_WEIGHT) {
            $qty = number_format($cant, 1).'kg';
        } else {
            $qty = (string) (int) round($cant);
        }

        $sub = (float) $detalle->subtotal;
        $monto = '$'.number_format($sub, 2, '.', ',');
        $concepto = $qty.'x '.$nombre;
        $espacio = max(1, $ancho - mb_strlen($concepto) - mb_strlen($monto));
        $out = [mb_substr($concepto.str_repeat(' ', $espacio).$monto, 0, $ancho)];

        if (($detalle->billing_mode ?? '') === OrdenLavanderiaDetalle::BILLING_BY_WEIGHT && $detalle->weight_kg) {
            $out[] = '  Peso: '.number_format((float) $detalle->weight_kg, 1).' kg';
        }

        $load = $detalle->loadType?->name;
        if ($load) {
            $out[] = '  Carga: '.$this->normalizarTexto($load);
        }

        $obs = trim((string) ($detalle->observations ?? ''));
        if ($obs !== '') {
            foreach ($this->partirLineas('Obs: '.$this->normalizarTexto($obs), $ancho - 2) as $ol) {
                $out[] = '  '.$ol;
            }
        }

        return $out;
    }

    private function lineaImporte(string $etiqueta, float $importe, int $ancho): string
    {
        $monto = '$'.number_format($importe, 2, '.', ',');
        $espacio = max(1, $ancho - strlen($etiqueta) - strlen($monto));

        return $etiqueta.str_repeat(' ', $espacio).$monto;
    }

    private function etiquetaMetodoPago(string $metodo): string
    {
        return match ($metodo) {
            'efectivo' => 'Efectivo',
            'tarjeta' => 'Tarjeta',
            'transferencia' => 'Transferencia',
            default => ucfirst($metodo),
        };
    }

    /**
     * @return list<string>
     */
    private function partirLineas(string $texto, int $ancho): array
    {
        $texto = trim($texto);
        if ($texto === '') {
            return [];
        }

        return explode("\n", wordwrap($texto, max(10, $ancho), "\n", true));
    }
}
