import { createReadStream } from 'node:fs';
import { mkdir, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { grpc, config, getMeta, getFolder, jsonToCsv } from '@opengis/fastify-table/utils.js';

import getInfo from './funcs/get.info.js';
import ContentType from './funcs/content.type.js';

const { jsonToXls, geojsonToShp } = grpc();
const rootDir = getFolder(config, 'local');

/**
 * Експорт даних реєстру
 * @param {Object} query - Об'єкт запиту з форматом.
 * @param {Object} params - Об'єкт параметрів з slug та type.
 * @param {Function} reply 
 * @returns {boolean} File
 */

export default async function gisExport({ pg, query = {}, params = {} }, reply) {
    const { format } = query;
    const { slug, type } = params;
    if (!['csv', 'xlsx', 'shp', 'geojson'].includes(format)) {
        return reply.code(401).send({ message: 'Param format is invalid. Allowed: csv, xlsx, shp, geojson', status: 404 });
    }
    if (!slug) return reply.code(401).send({ message: 'slug is not defined', status: 404 });
    if (!type) return reply.code(401).send({ message: 'type is not defined', status: 404 });

    let registerInfo;
    let serviceInfo;

    if (type === 'register') {
        registerInfo = await pg.query(
            `SELECT table_name, columns, query
            FROM gis.registers
            WHERE register_key = $1`,
            [slug]
        );
    }
    if (type === 'service') {
        serviceInfo = await pg.query(
            `SELECT source_path as table_name, attributes as columns, query
            FROM gis.services
            WHERE service_key = $1`,
            [slug]
        );
    }
    const finalInfo = registerInfo?.rows[0] || serviceInfo?.rows[0];

    const exportable = finalInfo?.columns?.filter(c => c.is_export);
    const colmodel = exportable.map(col => ({
        name: col.name,
        title: col.ua || col.name
    }));
    const { rows, table, srid, sqlList } = await getInfo({ finalInfo, format }, reply);
    let filePath = ['csv', 'geojson', 'xlsx'].includes(format) ? path.join(rootDir, `/files/tmp/export_${table}_${Date.now()}.${format}`) : path.join(rootDir, `/files/tmp/export_${table}_${Date.now()}.zip`);
    const ext = path.extname(filePath);
    const filePathJSON = ['csv', 'geojson', 'shp'].includes(format) ? filePath.replace(ext, '.json') : filePath;
    let exportedZipPaths = [];

    const meta = await getMeta({ pg, table: table });
    if ((format === 'geojson' || format === 'shp') && !meta?.geom) {
        return reply.status(400).send('Ця таблиця не містить полів геометрії. Виберіть тип, який не потребує геометрії для вивантаження');
    }

    if (format === 'xlsx') {
        if (!jsonToXls) {
            const message = 'Сервіс конвертації jsonToXls тимчасово недоступний. Спробуйте експортувати дані у іншому форматі...';
            const error = new Error(message);
            error.status = 503;

            throw error;
        }
        const { result } = await jsonToXls({
            json: JSON.stringify(rows),
            colmodel: JSON.stringify(colmodel),
        });

        await mkdir(path.dirname(filePath), { recursive: true });
        await writeFile(filePath, result, 'base64');
    }

    if (format === 'csv') {
        await mkdir(path.dirname(filePathJSON), { recursive: true });
        await writeFile(filePathJSON, JSON.stringify(rows));

        await jsonToCsv({
            filePath: filePathJSON, colmodel, columnList: colmodel.map(c => c.name),
        });
    }

    if (['shp', 'geojson'].includes(format)) {
        if (format === 'geojson') {
            const geojson = {
                type: 'FeatureCollection',
                features: rows.map((row) => ({
                    type: 'Feature',
                    geometry: row.geom,
                    properties: Object.fromEntries(Object.entries(row).filter(([key]) => key !== 'geom')),
                })),
            };
            filePath = filePathJSON.replace('.json', '.geojson');
            await mkdir(path.dirname(filePath), { recursive: true });
            await writeFile(filePath, JSON.stringify(geojson));
        }

        if (format === 'shp') {
            const proj = `EPSG:${srid || 4326}`;
            for (const { type, query } of sqlList) {
                const { rows } = await pg.query(query);
                if (!rows.length) continue;

                const geojson = {
                    type: 'FeatureCollection',
                    features: rows.map(row => ({
                        type: 'Feature',
                        geometry: row.geom,
                        properties: Object.fromEntries(Object.entries(row).filter(([k]) => k !== 'geom')),
                    })),
                };
                const geomShort = type.toLowerCase().replace('st_', '');
                const typedPath = filePath.replace('.zip', `_${geomShort}.zip`);

                const filePathTyped = filePath.replace('.zip', `_${type.toLowerCase().replace('st_', '')}.zip`);
                const { result: shpZipBuffer } = await geojsonToShp({
                    geojson: JSON.stringify(geojson),
                    proj,
                });

                await mkdir(path.dirname(filePathTyped), { recursive: true });
                await writeFile(filePathTyped, Buffer.from(shpZipBuffer, 'base64'));

                exportedZipPaths.push(typedPath);
            }
        }
    }

    let exportFilePath = filePath;

    if (format === 'shp' && exportedZipPaths?.length > 0) {
        exportFilePath = exportedZipPaths[0];
    }

    reply.header('Content-Disposition', `attachment; filename=${encodeURIComponent(path.basename(exportFilePath))}`);
    reply.header('Content-Type', ContentType[format]);

    const fileStream = createReadStream(exportFilePath);
    return reply.send(fileStream);
}