<?php

/**
 * ExportController
 *
 * Centralises all export logic for admin-facing data dumps.
 *
 * Responsibilities:
 *  - Export the full SQLite database as a .sqlite file download
 *  - Export TFE listings as CSV (the reverse of the CSV import)
 *
 * The class has NO output side-effects; the thin dispatcher files
 * (public/admin/actions/…) perform headers and echo.
 */
class ExportController
{
    private Database $db;

    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    public static function create(): self
    {
        require_once APP_ROOT . '/src/Database.php';
        return new self(Database::getInstance());
    }

    // ── Database export ──────────────────────────────────────────────────

    /**
     * Return the absolute path of the live database file.
     */
    public function getDatabasePath(): string
    {
        return $this->db->getDatabasePath();
    }

    // ── Files export ────────────────────────────────────────────────────

    /**
     * Fetch all thesis file records with their thesis identifier.
     *
     * @return list<array{id:int, thesis_id:int, identifier:?string, file_type:string,
     *                    file_path:string, file_name:string, file_size:?int,
     *                    mime_type:?string, description:?string, sort_order:int,
     *                    display_label:?string, file_hash:?string}>
     */
    public function getAllThesisFiles(): array
    {
        return $this->db->getAllThesisFilesForExport();
    }

    /**
     * Build a JSON manifest describing every thesis and its files.
     *
     * The manifest maps identifier → { title, files: [{type, path, name, size, mime, hash, label}] }
     * and is used on restore to re-link files to DB records.
     *
     * @return array
     */
    public function buildExportManifest(): array
    {
        $files   = $this->getAllThesisFiles();
        $theses  = $this->db->getAllThesesForExport();

        // Index theses by id for O(1) lookup
        $byId = [];
        foreach ($theses as $t) {
            $byId[(int) $t['id']] = $t;
        }

        $entries = [];
        foreach ($files as $f) {
            $tid = (int) $f['thesis_id'];
            $t   = $byId[$tid] ?? null;
            $key = $t['identifier'] ?? ('id_' . $tid);

            if (!isset($entries[$key])) {
                $entries[$key] = [
                    'id'         => $tid,
                    'identifier' => $t['identifier'] ?? null,
                    'title'      => $t['title']      ?? '(inconnu)',
                    'year'       => $t['year']       ?? 0,
                    'files'      => [],
                ];
            }

            $entries[$key]['files'][] = [
                'type'      => $f['file_type'],
                'path'      => $f['file_path'],
                'name'      => $f['file_name'],
                'size'      => $f['file_size'],
                'mime'      => $f['mime_type'],
                'hash'      => $f['file_hash'] ?? null,
                'label'     => $f['display_label'] ?? null,
                'sort_order' => (int) $f['sort_order'],
            ];
        }

        return [
            'exported_at' => date('c'),
            'db_file'     => basename($this->db->getDatabasePath()),
            'total_theses' => count($entries),
            'total_files'  => count($files),
            'theses'       => $entries,
        ];
    }

    /**
     * Create a zip archive containing all thesis files under a files/
     * directory and a manifest.json at the root.
     *
     * Returns the path to the temporary zip file. Caller is responsible
     * for unlink() after streaming.
     *
     * @param  string|null $baseDir  Base directory path for files inside the zip.
     *                               Defaults to "files" (so files are at "files/theses/...").
     * @return string Absolute path to the generated zip file.
     * @throws Exception if zip creation fails.
     */
    public function createExportZip(?string $baseDir = null): string
    {
        $baseDir ??= 'files';
        $storageRoot = defined('STORAGE_ROOT') ? STORAGE_ROOT : APP_ROOT . '/storage';
        $files = $this->getAllThesisFiles();
        $manifest = $this->buildExportManifest();

        $tmpPath = tempnam(sys_get_temp_dir(), 'xamxam-export-');
        if ($tmpPath === false) {
            throw new Exception('Impossible de créer un fichier temporaire.');
        }

        // tempnam creates a regular file; we want a .zip file instead.
        unlink($tmpPath);
        $tmpPath .= '.zip';

        $zip = new ZipArchive();
        if ($zip->open($tmpPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
            throw new Exception('Impossible de créer l\'archive ZIP.');
        }

        // Add manifest.json at the root
        $zip->addFromString(
            'manifest.json',
            json_encode($manifest, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
        );

        // Add every thesis file under files/
        $addedCount   = 0;
        $skippedCount = 0;
        foreach ($files as $f) {
            $fullPath = $storageRoot . '/' . $f['file_path'];
            if (!is_file($fullPath) || !is_readable($fullPath)) {
                $skippedCount++;
                continue;
            }
            $zipPath = $baseDir . '/' . $f['file_path'];
            $zip->addFile($fullPath, $zipPath);
            $addedCount++;
        }

        $zip->addFromString(
            'manifest.json',
            json_encode(array_merge($manifest, [
                'zip_skipped_count' => $skippedCount,
                'zip_added_count'   => $addedCount,
            ]), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
        );

        if (!$zip->close()) {
            @unlink($tmpPath);
            throw new Exception('Erreur lors de la finalisation de l\'archive ZIP.');
        }

        return $tmpPath;
    }

    // ── CSV export ───────────────────────────────────────────────────────

    /**
     * Column headers matching the import format.
     */
    public const CSV_HEADERS = [
        'Identifiant',
        'Titre',
        'Sous-titre',
        'Auteur·ice(s)',
        'Contact',
        'Promoteur·ice(s)',
        'Format(s)',
        'Année',
        'AP',
        'Orientation',
        'Finalité',
        'Mots-clés',
        'Synopsis',
        'Contexte',
        'Remarques',
        'Langue',
        'Autorisation',
        'Licence',
        'Points sur 20',
        'Lien BAIU',
    ];

    /**
     * Fetch all theses and their related data, then return a list of rows
     * shaped to match the import CSV column order.
     *
     * Uses batch queries (one per related table) to avoid N+1.
     *
     * @return list<list<string>>   Each inner list has CSV_HEADERS_COUNT elements.
     */
    public function exportAllTheses(): array
    {
        // 1) Base thesis data (includes license_name via migration; fallback to license_type from the view)
        $theses = $this->db->getAllThesesForExport();
        if ($theses === []) {
            return [];
        }

        // 2) Load related data in batches
        $byThesis = function (array $rows): array {
            $map = [];
            foreach ($rows as $r) {
                $tid = (int) $r['thesis_id'];
                $map[$tid][] = $r;
            }
            return $map;
        };

        $authors   = $byThesis($this->db->getAllThesisAuthorsForExport());
        $supervisors = $byThesis($this->db->getAllThesisSupervisorsForExport());
        $tags      = $byThesis($this->db->getAllThesisTagsForExport());
        $languages = $byThesis($this->db->getAllThesisLanguagesForExport());
        $formats   = $byThesis($this->db->getAllThesisFormatsForExport());

        // 3) Build CSV rows
        $csvRows = [];
        foreach ($theses as $t) {
            $tid   = (int) $t['id'];

            // Authors + contact (first author with email)
            $authorList = [];
            $contact    = '';
            foreach (($authors[$tid] ?? []) as $a) {
                $authorList[] = $a['name'];
                if ($contact === '' && !empty($a['email'])) {
                    $contact = $a['email'];
                }
            }

            // Supervisors
            $supList = [];
            foreach (($supervisors[$tid] ?? []) as $s) {
                $supList[] = $s['name'];
            }

            // Tags
            $tagList = [];
            foreach (($tags[$tid] ?? []) as $tg) {
                $tagList[] = $tg['name'];
            }

            // Languages
            $langList = [];
            foreach (($languages[$tid] ?? []) as $l) {
                $langList[] = $l['name'];
            }

            // Formats
            $fmtList = [];
            foreach (($formats[$tid] ?? []) as $f) {
                $fmtList[] = $f['name'];
            }

            $csvRows[] = [
                $t['identifier']    ?? '',
                $t['title']         ?? '',
                $t['subtitle']      ?? '',
                implode(', ', $authorList),
                $contact,
                implode(', ', $supList),
                implode(', ', $fmtList),
                $t['year']               ?? '',
                $t['ap_program']         ?? '',
                $t['orientation']        ?? '',
                $t['finality_type']      ?? '',
                implode(', ', $tagList),
                $t['synopsis']           ?? '',
                $t['context_note']       ?? '',
                $t['remarks']            ?? '',
                implode(', ', $langList),
                $t['access_type']        ?? '',
                $t['license_name']       ?? '',
                isset($t['jury_points']) ? (string) $t['jury_points'] : '',
                $t['baiu_link']          ?? '',
            ];
        }

        return $csvRows;
    }
}
