<?php

namespace App\Support;

use App\Models\InventoryItem;

class ItemQrPdf
{
    private const PAGE_WIDTH = 595.28;
    private const PAGE_HEIGHT = 841.89;

    public function render(InventoryItem $item): string
    {
        $item->loadMissing(['category', 'room']);

        $qrJpeg = $this->qrJpeg($item->scanUrl());
        $imageSize = getimagesizefromstring($qrJpeg) ?: [420, 420];
        $content = $this->contentStream($item);

        return $this->buildPdf($content, $qrJpeg, $imageSize[0], $imageSize[1]);
    }

    private function contentStream(InventoryItem $item): string
    {
        $qrSize = 300;
        $qrX = (self::PAGE_WIDTH - $qrSize) / 2;
        $qrY = (self::PAGE_HEIGHT - $qrSize) / 2 - 18;
        $panelX = $qrX - 22;
        $panelY = $qrY - 22;
        $panelSize = $qrSize + 44;

        $stream = "1 1 1 rg 0 0 " . $this->num(self::PAGE_WIDTH) . ' ' . $this->num(self::PAGE_HEIGHT) . " re f\n";
        $stream .= "0.97 0.98 1 rg {$this->num($panelX)} {$this->num($panelY)} {$this->num($panelSize)} {$this->num($panelSize)} re f\n";
        $stream .= "0.89 0.92 0.97 RG {$this->num($panelX)} {$this->num($panelY)} {$this->num($panelSize)} {$this->num($panelSize)} re S\n";

        $titleLines = $this->wrapText($item->nama_barang, 34);
        $titleY = 766;

        foreach ($titleLines as $index => $line) {
            $stream .= $this->centeredText($line, $index === 0 ? 25 : 21, $titleY - ($index * 30), 'F1', '0.08 0.09 0.18');
        }

        $subtitleY = $titleY - (count($titleLines) * 30) - 8;
        $subtitle = $item->kode_aset . ' - ' . $item->room->nama_ruangan;
        $stream .= $this->centeredText($subtitle, 12, $subtitleY, 'F2', '0.45 0.48 0.6');
        $stream .= "q {$qrSize} 0 0 {$qrSize} {$this->num($qrX)} {$this->num($qrY)} cm /Im1 Do Q\n";

        return $stream;
    }

    private function qrJpeg(string $data): string
    {
        if (! app()->runningUnitTests()) {
            $url = 'https://api.qrserver.com/v1/create-qr-code/?size=420x420&format=jpg&margin=18&data=' . rawurlencode($data);
            $context = stream_context_create([
                'http' => [
                    'timeout' => 8,
                    'ignore_errors' => true,
                    'header' => "User-Agent: LaravelInventoryQr/1.0\r\n",
                ],
            ]);
            $jpeg = @file_get_contents($url, false, $context);

            if (is_string($jpeg) && str_starts_with($jpeg, "\xFF\xD8")) {
                return $jpeg;
            }
        }

        return $this->fallbackJpeg();
    }

    private function fallbackJpeg(): string
    {
        $size = 420;
        $image = imagecreatetruecolor($size, $size);
        $white = imagecolorallocate($image, 255, 255, 255);
        $blue = imagecolorallocate($image, 29, 127, 245);
        $ink = imagecolorallocate($image, 23, 24, 50);

        imagefill($image, 0, 0, $white);

        for ($y = 0; $y < 14; $y++) {
            for ($x = 0; $x < 14; $x++) {
                if (($x * 5 + $y * 3 + $x * $y) % 4 === 0) {
                    imagefilledrectangle($image, 36 + ($x * 25), 36 + ($y * 25), 53 + ($x * 25), 53 + ($y * 25), $ink);
                }
            }
        }

        imagefilledrectangle($image, 42, 42, 112, 112, $blue);
        imagefilledrectangle($image, 72, 72, 95, 95, $white);
        imagefilledrectangle($image, 308, 42, 378, 112, $blue);
        imagefilledrectangle($image, 335, 72, 358, 95, $white);
        imagefilledrectangle($image, 42, 308, 112, 378, $blue);
        imagefilledrectangle($image, 72, 335, 95, 358, $white);

        ob_start();
        imagejpeg($image, null, 92);
        $jpeg = ob_get_clean();
        imagedestroy($image);

        return $jpeg ?: '';
    }

    private function buildPdf(string $content, string $jpeg, int $imageWidth, int $imageHeight): string
    {
        $objects = [
            1 => "<< /Type /Catalog /Pages 2 0 R >>",
            2 => "<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
            3 => '<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ' . $this->num(self::PAGE_WIDTH) . ' ' . $this->num(self::PAGE_HEIGHT) . '] /Resources << /Font << /F1 4 0 R /F2 7 0 R >> /XObject << /Im1 5 0 R >> >> /Contents 6 0 R >>',
            4 => '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold >>',
            5 => "<< /Type /XObject /Subtype /Image /Width {$imageWidth} /Height {$imageHeight} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length " . strlen($jpeg) . " >>\nstream\n{$jpeg}\nendstream",
            6 => '<< /Length ' . strlen($content) . " >>\nstream\n{$content}endstream",
            7 => '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>',
        ];

        $pdf = "%PDF-1.4\n%\xE2\xE3\xCF\xD3\n";
        $offsets = [0];

        foreach ($objects as $number => $object) {
            $offsets[$number] = strlen($pdf);
            $pdf .= "{$number} 0 obj\n{$object}\nendobj\n";
        }

        $xrefOffset = strlen($pdf);
        $pdf .= "xref\n0 " . (count($objects) + 1) . "\n";
        $pdf .= "0000000000 65535 f \n";

        for ($number = 1; $number <= count($objects); $number++) {
            $pdf .= sprintf("%010d 00000 n \n", $offsets[$number]);
        }

        $pdf .= "trailer\n<< /Size " . (count($objects) + 1) . " /Root 1 0 R >>\n";
        $pdf .= "startxref\n{$xrefOffset}\n%%EOF";

        return $pdf;
    }

    private function centeredText(string $text, int $fontSize, float $y, string $font, string $color): string
    {
        $x = max(42, (self::PAGE_WIDTH - $this->estimatedWidth($text, $fontSize)) / 2);
        $safeText = $this->pdfText($text);

        return "BT /{$font} {$fontSize} Tf {$color} rg 1 0 0 1 {$this->num($x)} {$this->num($y)} Tm ({$safeText}) Tj ET\n";
    }

    /**
     * @return list<string>
     */
    private function wrapText(string $text, int $limit): array
    {
        $words = preg_split('/\s+/', trim($text)) ?: [];
        $lines = [];
        $line = '';

        foreach ($words as $word) {
            $candidate = trim($line . ' ' . $word);

            if (strlen($candidate) > $limit && $line !== '') {
                $lines[] = $line;
                $line = $word;
            } else {
                $line = $candidate;
            }
        }

        if ($line !== '') {
            $lines[] = $line;
        }

        return array_slice($lines ?: [$text], 0, 2);
    }

    private function pdfText(string $text): string
    {
        $text = preg_replace('/\s+/', ' ', trim($text)) ?: '';
        $encoded = @iconv('UTF-8', 'Windows-1252//TRANSLIT//IGNORE', $text);
        $encoded = is_string($encoded) ? $encoded : $text;

        return strtr($encoded, [
            '\\' => '\\\\',
            '(' => '\\(',
            ')' => '\\)',
        ]);
    }

    private function estimatedWidth(string $text, int $fontSize): float
    {
        return strlen($text) * $fontSize * 0.52;
    }

    private function num(float $number): string
    {
        return rtrim(rtrim(number_format($number, 2, '.', ''), '0'), '.');
    }
}
