<?php

namespace App\Libraries;

use CodeIgniter\HTTP\ResponseInterface;

/**
 * Shared CSV / PDF / Word download builder for tabular reports.
 *
 * PDF uses PDF 1.4 core Helvetica. Word builds a minimal .docx via ZipArchive.
 * No external packages required.
 */
class ReportExporter
{
    /**
     * @param list<string>              $headers
     * @param list<list<string|int|float|null>> $rows
     */
    public function download(
        string $format,
        string $filename,
        string $title,
        array $headers,
        array $rows,
        ?string $subtitle = null
    ): ResponseInterface {
        return $this->downloadSections($format, $filename, $title, [
            ['title' => null, 'headers' => $headers, 'rows' => $rows],
        ], $subtitle);
    }

    /**
     * @param list<array{title?: ?string, headers: list<string>, rows: list<list<string|int|float|null>>}> $sections
     */
    public function downloadSections(
        string $format,
        string $filename,
        string $title,
        array $sections,
        ?string $subtitle = null
    ): ResponseInterface {
        $format = strtolower($format);

        if ($format === 'pdf') {
            return $this->pdfResponse($filename, $title, $sections, $subtitle);
        }

        if ($format === 'word' || $format === 'docx') {
            return $this->wordResponse($filename, $title, $sections, $subtitle);
        }

        return $this->csvResponse($filename, $title, $sections, $subtitle);
    }

    /**
     * @param list<array{title?: ?string, headers: list<string>, rows: list<list<string|int|float|null>>}> $sections
     */
    private function csvResponse(string $filename, string $title, array $sections, ?string $subtitle): ResponseInterface
    {
        $fh = fopen('php://temp', 'r+');
        fwrite($fh, "\xEF\xBB\xBF"); // UTF-8 BOM for Excel
        fputcsv($fh, [$title]);
        if ($subtitle !== null && $subtitle !== '') {
            fputcsv($fh, [$subtitle]);
        }
        fputcsv($fh, ['Generated', date('Y-m-d H:i')]);
        fputcsv($fh, []);

        foreach ($sections as $section) {
            $sectionTitle = $section['title'] ?? null;
            if ($sectionTitle !== null && $sectionTitle !== '') {
                fputcsv($fh, [$sectionTitle]);
            }
            fputcsv($fh, $section['headers']);
            foreach ($section['rows'] as $row) {
                fputcsv($fh, array_map(static fn ($v) => $v === null ? '' : (string) $v, $row));
            }
            fputcsv($fh, []);
        }

        rewind($fh);
        $body = stream_get_contents($fh) ?: '';
        fclose($fh);

        return service('response')
            ->setHeader('Content-Type', 'text/csv; charset=UTF-8')
            ->setHeader('Content-Disposition', 'attachment; filename="' . $this->safeFilename($filename) . '.csv"')
            ->setBody($body);
    }

    /**
     * @param list<array{title?: ?string, headers: list<string>, rows: list<list<string|int|float|null>>}> $sections
     */
    private function pdfResponse(string $filename, string $title, array $sections, ?string $subtitle): ResponseInterface
    {
        $pdf = new SimpleTablePdf($title, $subtitle);
        foreach ($sections as $section) {
            $pdf->addSection($section['title'] ?? null, $section['headers'], $section['rows']);
        }
        $body = $pdf->render();

        return service('response')
            ->setHeader('Content-Type', 'application/pdf')
            ->setHeader('Content-Disposition', 'attachment; filename="' . $this->safeFilename($filename) . '.pdf"')
            ->setBody($body);
    }

    /**
     * @param list<array{title?: ?string, headers: list<string>, rows: list<list<string|int|float|null>>}> $sections
     */
    private function wordResponse(string $filename, string $title, array $sections, ?string $subtitle): ResponseInterface
    {
        $body = $this->buildDocx($title, $sections, $subtitle);

        return service('response')
            ->setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document')
            ->setHeader('Content-Disposition', 'attachment; filename="' . $this->safeFilename($filename) . '.docx"')
            ->setBody($body);
    }

    /**
     * @param list<array{title?: ?string, headers: list<string>, rows: list<list<string|int|float|null>>}> $sections
     */
    private function buildDocx(string $title, array $sections, ?string $subtitle): string
    {
        $xml = [];
        $xml[] = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
        $xml[] = '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">';
        $xml[] = '<w:body>';
        $xml[] = $this->wordParagraph($title, true, 28);
        if ($subtitle !== null && $subtitle !== '') {
            $xml[] = $this->wordParagraph($subtitle, false, 20);
        }
        $xml[] = $this->wordParagraph('Generated: ' . date('Y-m-d H:i'), false, 18);

        foreach ($sections as $section) {
            $sectionTitle = $section['title'] ?? null;
            if ($sectionTitle !== null && $sectionTitle !== '') {
                $xml[] = $this->wordParagraph((string) $sectionTitle, true, 22);
            }

            $headers = $section['headers'] ?? [];
            $rows    = $section['rows'] ?? [];
            if ($headers === []) {
                continue;
            }

            $colCount = max(1, count($headers));
            $xml[]    = '<w:tbl>';
            $xml[]    = '<w:tblPr><w:tblW w:w="5000" w:type="pct"/><w:tblBorders>'
                . '<w:top w:val="single" w:sz="4" w:color="999999"/>'
                . '<w:left w:val="single" w:sz="4" w:color="999999"/>'
                . '<w:bottom w:val="single" w:sz="4" w:color="999999"/>'
                . '<w:right w:val="single" w:sz="4" w:color="999999"/>'
                . '<w:insideH w:val="single" w:sz="4" w:color="CCCCCC"/>'
                . '<w:insideV w:val="single" w:sz="4" w:color="CCCCCC"/>'
                . '</w:tblBorders></w:tblPr>';
            $xml[] = $this->wordTableRow($headers, true, $colCount);
            foreach ($rows as $row) {
                $cells = [];
                for ($i = 0; $i < $colCount; $i++) {
                    $cells[] = $row[$i] ?? '';
                }
                $xml[] = $this->wordTableRow($cells, false, $colCount);
            }
            $xml[] = '</w:tbl>';
            $xml[] = $this->wordParagraph('');
        }

        $xml[] = '<w:sectPr><w:pgSz w:w="16838" w:h="11906" w:orient="landscape"/>'
            . '<w:pgMar w:top="720" w:right="720" w:bottom="720" w:left="720"/></w:sectPr>';
        $xml[] = '</w:body></w:document>';

        $documentXml = implode('', $xml);
        $tmp         = tempnam(sys_get_temp_dir(), 'docx');
        if ($tmp === false) {
            throw new \RuntimeException('Unable to create temporary file for Word export.');
        }

        $zip = new \ZipArchive();
        if ($zip->open($tmp, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) {
            @unlink($tmp);
            throw new \RuntimeException('Unable to build Word document.');
        }

        $zip->addFromString('[Content_Types].xml', '<?xml version="1.0" encoding="UTF-8"?>'
            . '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
            . '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'
            . '<Default Extension="xml" ContentType="application/xml"/>'
            . '<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>'
            . '</Types>');
        $zip->addFromString('_rels/.rels', '<?xml version="1.0" encoding="UTF-8"?>'
            . '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
            . '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>'
            . '</Relationships>');
        $zip->addFromString('word/document.xml', $documentXml);
        $zip->close();

        $body = file_get_contents($tmp) ?: '';
        @unlink($tmp);

        return $body;
    }

    private function wordParagraph(string $text, bool $bold = false, int $sizeHalfPoints = 20): string
    {
        $runProps = '<w:rPr><w:sz w:val="' . $sizeHalfPoints . '"/>';
        if ($bold) {
            $runProps .= '<w:b/>';
        }
        $runProps .= '</w:rPr>';

        return '<w:p><w:r>' . $runProps . '<w:t xml:space="preserve">'
            . $this->xmlEscape($text) . '</w:t></w:r></w:p>';
    }

    /**
     * @param list<string|int|float|null> $cells
     */
    private function wordTableRow(array $cells, bool $header, int $colCount): string
    {
        $out = '<w:tr>';
        for ($i = 0; $i < $colCount; $i++) {
            $value = $cells[$i] ?? '';
            $shade = $header ? '<w:shd w:val="clear" w:fill="EEEEEE"/>' : '';
            $bold  = $header ? '<w:b/>' : '';
            $out  .= '<w:tc><w:tcPr>' . $shade . '</w:tcPr><w:p><w:r><w:rPr>' . $bold
                . '<w:sz w:val="16"/></w:rPr><w:t xml:space="preserve">'
                . $this->xmlEscape((string) ($value === null ? '' : $value))
                . '</w:t></w:r></w:p></w:tc>';
        }

        return $out . '</w:tr>';
    }

    private function xmlEscape(string $value): string
    {
        return htmlspecialchars($value, ENT_XML1 | ENT_QUOTES, 'UTF-8');
    }

    private function safeFilename(string $name): string
    {
        $name = preg_replace('/[^A-Za-z0-9_\-]+/', '_', $name) ?? 'report';

        return trim($name, '_') ?: 'report';
    }
}
