<?php

namespace App\Services;

use App\Models\Application;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use RuntimeException;
use setasign\Fpdi\Fpdi;

class ParticularApplicationPdf
{
    private array $temporaryImages = [];

    public function generate(Application $application, bool $showGrid = false): string
    {
        $config = config('pdf_templates.'.$application->type);
        if (! is_array($config)) {
            throw new RuntimeException('No existe una plantilla PDF para este tipo de inscripción.');
        }
        $template = $config['template'];
        if (! is_file($template)) {
            throw new RuntimeException('No se encontró la plantilla PDF de inscripción particular.');
        }

        $pdf = new Fpdi('P', 'pt');
        $pdf->SetAutoPageBreak(false);
        $pdf->SetMargins(0, 0, 0);
        $pdf->SetTitle("Inscripción {$application->folio}", true);
        $pdf->SetAuthor('Centro de Capacitación y Recreación', true);

        try {
            $pageCount = $pdf->setSourceFile($template);
            $values = $this->values($application);

            for ($pageNumber = 1; $pageNumber <= $pageCount; $pageNumber++) {
                $templateId = $pdf->importPage($pageNumber);
                $size = $pdf->getTemplateSize($templateId);
                $pdf->AddPage($size['orientation'], [$size['width'], $size['height']]);
                $pdf->useTemplate($templateId);

                $this->writeConfiguredFields($pdf, $config, $values, $pageNumber);
                $this->writeDependents($pdf, $config, $application->data['dependents'] ?? [], $pageNumber);
                $this->writeBoardMembers($pdf, $config, $application->data['board_members'] ?? [], $pageNumber);
                $this->writeImages($pdf, $config, $application->data, $pageNumber);

                if ($showGrid) {
                    $this->drawGrid($pdf, $config, $size['width'], $size['height'], $pageNumber);
                }
            }

            return $pdf->Output('S');
        } finally {
            foreach ($this->temporaryImages as $path) {
                @unlink($path);
            }
            $this->temporaryImages = [];
        }
    }

    private function values(Application $application): array
    {
        $data = $application->data;
        if ($application->type === 'particular') {
            $data['work_area'] = $data['company'] ?? '';
            $data['superintendency'] = $data['job_title'] ?? '';
            $data['management'] = '';
        }
        $applicationDate = $this->dateParts($data['application_date'] ?? $application->application_date);
        $signatureDate = $this->dateParts($data['signature_date'] ?? null);

        return [
            ...Arr::only($data, [
                'payment_amount', 'receipt_number', 'full_name', 'rut', 'email', 'address', 'phone',
                'delivery_address', 'profession', 'work_area', 'superintendency', 'work_phone',
                'management', 'work_email', 'recommender_name', 'recommender_rut',
            ]),
            'birth_date' => $this->formatDate($data['birth_date'] ?? null),
            'first_payment_date' => $this->formatDate($data['first_payment_date'] ?? null),
            'application_date_day' => $applicationDate['day'],
            'application_date_month' => $applicationDate['month'],
            'application_date_year' => $applicationDate['year'],
            'signature_date_day' => $signatureDate['day'],
            'signature_date_month' => $signatureDate['month'],
            'signature_date_year' => $signatureDate['year'],
        ];
    }

    private function writeConfiguredFields(Fpdi $pdf, array $config, array $values, int $page): void
    {
        foreach ($config['fields'] as $key => $field) {
            if (($field['page'] ?? $config['defaults']['page']) !== $page) {
                continue;
            }
            $this->writeText($pdf, (string) ($values[$key] ?? ''), [...$config['defaults'], ...$field]);
        }
    }

    private function writeDependents(Fpdi $pdf, array $config, array $dependents, int $page): void
    {
        $table = $config['dependents'];
        if (($table['page'] ?? 1) !== $page) {
            return;
        }
        foreach (array_slice($dependents, 0, $table['max_rows']) as $row => $dependent) {
            $birthDate = $this->dateParts($dependent['birth_date'] ?? null);
            $values = [
                'full_name' => $dependent['full_name'] ?? '',
                'rut' => $dependent['rut'] ?? '',
                'birth_date_day' => $birthDate['day'],
                'birth_date_month' => $birthDate['month'],
                'birth_date_year' => $birthDate['year'],
                'relationship' => $dependent['relationship'] ?? '',
            ];
            foreach ($table['columns'] as $key => $column) {
                $this->writeText($pdf, (string) $values[$key], [
                    ...$config['defaults'], ...$column,
                    'y' => $table['start_y'] + ($row * $table['row_height']) + ($column['y_offset'] ?? 0),
                    'h' => $table['row_height'] - 3,
                ]);
            }
        }
    }

    private function writeBoardMembers(Fpdi $pdf, array $config, array $members, int $page): void
    {
        $table = $config['board_members'];
        if (($table['page'] ?? 1) !== $page) {
            return;
        }
        foreach (array_slice($members, 0, $table['max_rows']) as $row => $member) {
            foreach ($table['columns'] as $key => $column) {
                $this->writeText($pdf, (string) ($member[$key] ?? ''), [
                    ...$config['defaults'], ...$column,
                    'y' => $table['start_y'] + ($row * $table['row_height']),
                    'h' => $table['row_height'] - 3,
                ]);
            }
            $signature = $member['signature'] ?? null;
            if ($signature) {
                $image = $config['board_signature'];
                $image['y'] = $image['start_y'] + ($row * $image['row_height']);
                $this->placeDataImage($pdf, $signature, $image, $page);
            }
        }
    }

    private function writeImages(Fpdi $pdf, array $config, array $data, int $page): void
    {
        foreach ($config['images'] as $key => $image) {
            $this->placeDataImage($pdf, $data[$key] ?? null, $image, $page);
        }
    }

    private function writeText(Fpdi $pdf, string $text, array $field): void
    {
        $text = trim(preg_replace('/\s+/u', ' ', $text));
        if ($text === '') {
            return;
        }

        [$x, $y, $width, $height] = [$field['x'], $field['y'], $field['w'], $field['h']];
        $size = (float) $field['size'];
        $minimum = (float) $field['min_size'];
        $pdf->SetTextColor(...$field['color']);
        $text = $this->pdfText($text);
        $pdf->SetFont('Helvetica', '', $size);

        while ($size > $minimum && $pdf->GetStringWidth($text) > $width - 4) {
            $size -= .25;
            $pdf->SetFont('Helvetica', '', $size);
        }
        if ($pdf->GetStringWidth($text) > $width - 4) {
            $text = $this->truncateToWidth($pdf, $text, $width - 4);
        }

        $lineHeight = min((float) $field['line_height'], $height);
        $pdf->SetXY($x + 2, $y + max(0, ($height - $lineHeight) / 2));
        $pdf->Cell($width - 4, $lineHeight, $text, 0, 0, $field['align'] ?? 'L');
    }

    private function truncateToWidth(Fpdi $pdf, string $text, float $width): string
    {
        $suffix = '...';
        while ($text !== '' && $pdf->GetStringWidth($text.$suffix) > $width) {
            $text = mb_substr($text, 0, -1);
        }
        return rtrim($text).$suffix;
    }

    private function placeDataImage(Fpdi $pdf, mixed $dataUri, array $box, int $page): void
    {
        if (! is_string($dataUri) || ($box['page'] ?? 1) !== $page || ! preg_match('#^data:image/(png|jpe?g);base64,(.+)$#s', $dataUri, $matches)) {
            return;
        }
        $binary = base64_decode($matches[2], true);
        $imageSize = $binary ? @getimagesizefromstring($binary) : false;
        if (! $imageSize) {
            return;
        }
        $type = str_starts_with($matches[1], 'jp') ? 'JPG' : 'PNG';
        $path = tempnam(sys_get_temp_dir(), 'ccr-pdf-');
        file_put_contents($path, $binary);
        $this->temporaryImages[] = $path;

        $scale = min($box['w'] / $imageSize[0], $box['h'] / $imageSize[1]);
        $width = $imageSize[0] * $scale;
        $height = $imageSize[1] * $scale;
        $x = $box['x'] + (($box['w'] - $width) / 2);
        $y = $box['y'] + (($box['h'] - $height) / 2);
        $pdf->Image($path, $x, $y, $width, $height, $type);
    }

    private function drawGrid(Fpdi $pdf, array $config, float $width, float $height, int $page): void
    {
        $pdf->SetDrawColor(0, 160, 210);
        $pdf->SetTextColor(0, 110, 160);
        $pdf->SetLineWidth(.25);
        $pdf->SetFont('Helvetica', '', 4);
        for ($x = 0; $x <= $width; $x += 25) {
            $pdf->Line($x, 0, $x, $height);
            if ($x % 50 === 0) $pdf->Text($x + 1, 7, (string) $x);
        }
        for ($y = 0; $y <= $height; $y += 25) {
            $pdf->Line(0, $y, $width, $y);
            if ($y % 50 === 0) $pdf->Text(1, $y + 6, (string) $y);
        }

        $pdf->SetDrawColor(220, 0, 90);
        $pdf->SetTextColor(180, 0, 70);
        foreach ($config['fields'] as $key => $field) {
            if (($field['page'] ?? 1) !== $page) continue;
            $pdf->Rect($field['x'], $field['y'], $field['w'], $field['h']);
            $pdf->Text($field['x'] + 1, $field['y'] + 5, $key);
        }
    }

    private function formatDate(mixed $date): string
    {
        if (! $date) return '';
        try { return Carbon::parse($date)->format('d/m/Y'); } catch (\Throwable) { return (string) $date; }
    }

    private function pdfText(string $text): string
    {
        return iconv('UTF-8', 'windows-1252//TRANSLIT', $text) ?: '';
    }

    private function dateParts(mixed $date): array
    {
        if (! $date) return ['day' => '', 'month' => '', 'year' => ''];
        try {
            $parsed = Carbon::parse($date);
            return ['day' => $parsed->format('d'), 'month' => $parsed->format('m'), 'year' => $parsed->format('Y')];
        } catch (\Throwable) {
            return ['day' => '', 'month' => '', 'year' => ''];
        }
    }
}
