<?php

namespace App\Services;

use App\Models\Impresora;
use App\Support\EscposGdImage;
use App\Support\SeguimientoBarcode;
use App\Support\ThermalLineWidth;
use Mike42\Escpos\PrintConnectors\FilePrintConnector;
use Mike42\Escpos\PrintConnectors\NetworkPrintConnector;
use Mike42\Escpos\PrintConnectors\WindowsPrintConnector;
use Mike42\Escpos\Printer;

class ImpresionService
{
    private const MARKER_BIG = '@@BIG:';

    private const MARKER_BARCODE = '@@BARCODE:';

    private const MARKER_CENTER = '@@CENTER:';

    private const MARKER_DASH = '@@DASH';

    private const MARKER_PAD = '@@PAD:';

    private const MARKER_LABEL_END = '@@LABEL_END';

    /**
     * @return array{success: bool, message?: string, error?: string}
     */
    public function imprimirEscPosLocal(
        string $nombreImpresora,
        string $contenido,
        string $tipoTicket = Impresora::TIPO_VENTA,
        ?Impresora $impresoraModel = null
    ): array {
        try {
            $ancho = ThermalLineWidth::charsFromImpresora($impresoraModel);
            $this->ejecutarEnImpresora($nombreImpresora, $impresoraModel, function (Printer $printer) use ($contenido, $ancho, $impresoraModel): void {
                $this->enviarContenidoTexto($printer, $contenido, $ancho, $impresoraModel);
            });

            $etiqueta = $impresoraModel?->nombre ?? $nombreImpresora;

            return [
                'success' => true,
                'message' => "Ticket enviado a {$etiqueta} (Local)",
            ];
        } catch (\Throwable $e) {
            return [
                'success' => false,
                'error' => $e->getMessage(),
            ];
        }
    }

    /**
     * @deprecated Use imprimirEscPosLocal() con contenido de TicketService::generarEtiquetaBolsa().
     *
     * @return array{success: bool, message?: string, error?: string}
     */
    public function imprimirEtiquetaBolsaEscPos(
        string $nombreImpresora,
        string $clienteNombre,
        string $folioOrden,
        int $indiceBolsa,
        int $totalBolsas,
        string $codigoBolsa,
        ?Impresora $impresoraModel = null
    ): array {
        $ancho = ThermalLineWidth::charsFromImpresora($impresoraModel);
        $contenido = implode("\n", [
            self::MARKER_DASH,
            self::MARKER_PAD.'1',
            self::MARKER_BIG.strtoupper($this->normalizarEtiqueta($clienteNombre)),
            self::MARKER_PAD.'1',
            self::MARKER_BARCODE.\App\Support\LaundryScanCode::barcodePayload(trim($codigoBolsa)),
            self::MARKER_CENTER.\App\Support\LaundryScanCode::barcodePayload(trim($codigoBolsa)),
            self::MARKER_PAD.'1',
            self::MARKER_DASH,
            self::MARKER_LABEL_END,
            '',
        ]);

        return $this->imprimirEscPosLocal(
            $nombreImpresora,
            $contenido,
            Impresora::TIPO_VENTA,
            $impresoraModel
        );
    }

    /**
     * @param  callable(Printer): void  $callback
     */
    private function ejecutarEnImpresora(string $nombreImpresora, ?Impresora $impresoraModel, callable $callback): void
    {
        $esUnc = str_starts_with($nombreImpresora, '\\\\') && ($impresoraModel?->ip === null || $impresoraModel->ip === '');

        if ($esUnc) {
            $tmp = tempnam(sys_get_temp_dir(), 'escpos_');
            if ($tmp === false) {
                throw new \RuntimeException('No se pudo crear archivo temporal para impresión UNC.');
            }
            $connector = new FilePrintConnector($tmp);
            $printer = new Printer($connector);
            $callback($printer);
            $printer->close();
            if (! @copy($tmp, $nombreImpresora)) {
                @unlink($tmp);
                throw new \RuntimeException('No se pudo enviar el ticket a la impresora UNC: '.$nombreImpresora);
            }
            @unlink($tmp);

            return;
        }

        $connector = $this->obtenerConector($nombreImpresora, $impresoraModel);
        $printer = new Printer($connector);
        $callback($printer);
        $printer->close();
    }

    private function enviarContenidoTexto(Printer $printer, string $contenido, int $ancho, ?Impresora $impresoraModel): void
    {
        $printer->initialize();
        $printer->setPrintWidth(ThermalLineWidth::dotsFromImpresora($impresoraModel));

        $lineas = preg_split("/\r\n|\n|\r/", $contenido) ?: [];
        $esEtiqueta = str_contains($contenido, self::MARKER_LABEL_END);

        foreach ($lineas as $idx => $linea) {
            $linea = rtrim((string) $linea, "\r\n");

            if ($linea === self::MARKER_LABEL_END) {
                continue;
            }

            if ($linea === self::MARKER_DASH) {
                $printer->setJustification(Printer::JUSTIFY_CENTER);
                $printer->text($this->lineaGuionDiscontinua($ancho)."\n");
                $printer->setJustification(Printer::JUSTIFY_LEFT);

                continue;
            }

            if (str_starts_with($linea, self::MARKER_PAD)) {
                $n = max(1, (int) substr($linea, strlen(self::MARKER_PAD)));
                $printer->feed($n);

                continue;
            }

            if (str_starts_with($linea, self::MARKER_CENTER)) {
                $texto = substr($linea, strlen(self::MARKER_CENTER));
                $printer->setJustification(Printer::JUSTIFY_CENTER);
                $printer->text($texto."\n");
                $printer->setJustification(Printer::JUSTIFY_LEFT);

                continue;
            }

            if (str_starts_with($linea, self::MARKER_BARCODE)) {
                $this->imprimirCodigoBarrasEnLinea($printer, substr($linea, strlen(self::MARKER_BARCODE)), $impresoraModel);

                continue;
            }

            if (str_starts_with($linea, self::MARKER_BIG)) {
                $texto = substr($linea, strlen(self::MARKER_BIG));
                $printer->setJustification(Printer::JUSTIFY_CENTER);
                $printer->setEmphasis(true);
                $printer->setTextSize(2, 2);
                $printer->text($texto."\n");
                $printer->setTextSize(1, 1);
                $printer->setEmphasis(false);
                $printer->setJustification(Printer::JUSTIFY_LEFT);

                continue;
            }

            if ($idx === 1 && $linea !== '' && ! str_starts_with($linea, '@@')) {
                $printer->setJustification(Printer::JUSTIFY_CENTER);
                $printer->setEmphasis(true);
                $printer->setTextSize(2, 2);
                $printer->text($linea."\n");
                $printer->setTextSize(1, 1);
                $printer->setEmphasis(false);
                $printer->setJustification(Printer::JUSTIFY_LEFT);

                continue;
            }

            if ($this->esSeparador($linea, '=', $ancho)) {
                $printer->text(str_repeat('=', $ancho)."\n");

                continue;
            }

            if ($this->esSeparador($linea, '-', $ancho)) {
                $printer->text(str_repeat('-', $ancho)."\n");

                continue;
            }

            $printer->text($linea."\n");
        }

        $printer->feed($esEtiqueta ? 3 : 5);
        $printer->cut();
        $printer->feed(2);
    }

    private function lineaGuionDiscontinua(int $ancho): string
    {
        $unit = '- ';
        $reps = max(6, (int) floor($ancho / strlen($unit)));

        return rtrim(str_repeat($unit, $reps));
    }

    private function esSeparador(string $linea, string $char, int $ancho): bool
    {
        $trim = trim($linea);

        return $trim !== '' && strlen($trim) >= 3 && strspn($trim, $char) === strlen($trim) && strlen($trim) >= min($ancho - 2, 3);
    }

    /**
     * @return FilePrintConnector|NetworkPrintConnector|WindowsPrintConnector
     */
    private function obtenerConector(string $nombreSistema, ?Impresora $impresora)
    {
        $ip = $impresora?->ip;
        if ($ip !== null && $ip !== '') {
            $puerto = (int) ($impresora->puerto ?? 9100);

            return new NetworkPrintConnector($ip, $puerto);
        }

        return new WindowsPrintConnector($nombreSistema);
    }

    private function imprimirCodigoBarrasEnLinea(Printer $printer, string $codigo, ?Impresora $impresoraModel): void
    {
        $codigo = trim($codigo);
        if ($codigo === '') {
            return;
        }

        $printer->setJustification(Printer::JUSTIFY_CENTER);
        $printer->feed(1);

        if ($this->imprimirCodigoBarrasComoImagen($printer, $codigo, $impresoraModel)) {
            $printer->feed(1);

            return;
        }

        if ($this->imprimirCodigoBarrasNativo($printer, $codigo)) {
            $printer->feed(1);

            return;
        }

        throw new \RuntimeException('No se pudo generar el codigo de barras para la etiqueta.');
    }

    private function imprimirCodigoBarrasComoImagen(Printer $printer, string $codigo, ?Impresora $impresoraModel): bool
    {
        if (! function_exists('imagecreatefromstring')) {
            return false;
        }

        $png = SeguimientoBarcode::pngBinary($codigo, 2, 90);
        if ($png === null) {
            return false;
        }

        $im = @imagecreatefromstring($png);
        if ($im === false) {
            return false;
        }

        $maxDots = (int) floor(ThermalLineWidth::dotsFromImpresora($impresoraModel) * 0.92);
        $w = imagesx($im);
        if ($w > $maxDots && $w > 0) {
            $h = imagesy($im);
            $newW = $maxDots;
            $newH = max(1, (int) round($h * ($newW / $w)));
            $scaled = imagescale($im, $newW, $newH);
            if ($scaled !== false) {
                imagedestroy($im);
                $im = $scaled;
            }
        }

        try {
            $img = new EscposGdImage;
            $img->readImageFromGdResource($im);
            if ($img->getWidth() <= 0 || $img->getHeight() <= 0) {
                return false;
            }
            $printer->bitImage($img);

            return true;
        } catch (\Throwable) {
            return false;
        } finally {
            imagedestroy($im);
        }
    }

    private function imprimirCodigoBarrasNativo(Printer $printer, string $codigo): bool
    {
        $codigo = strtoupper(trim($codigo));
        if ($codigo === '') {
            return false;
        }

        $printer->setBarcodeHeight(100);
        $printer->setBarcodeWidth(3);
        $printer->setBarcodeTextPosition(Printer::BARCODE_TEXT_NONE);

        try {
            $printer->barcode('{B'.$codigo, Printer::BARCODE_CODE128);

            return true;
        } catch (\Throwable) {
            // Continuar con CODE39.
        }

        $code39 = preg_replace('/[^0-9A-Z\\-\\.\\ \$\\/\\+\\%]/', '', $codigo) ?? '';
        if ($code39 === '') {
            return false;
        }

        try {
            $printer->barcode($code39, Printer::BARCODE_CODE39);

            return true;
        } catch (\Throwable) {
            return false;
        }
    }

    private function normalizarEtiqueta(string $texto): string
    {
        $map = [
            'á' => 'a', 'é' => 'e', 'í' => 'i', 'ó' => 'o', 'ú' => 'u',
            'Á' => 'A', 'É' => 'E', 'Í' => 'I', 'Ó' => 'O', 'Ú' => 'U',
            'ñ' => 'n', 'Ñ' => 'N', 'ü' => 'u', 'Ü' => 'U',
        ];

        return strtr(trim($texto), $map);
    }
}
