import path from 'node:path';
import { existsSync } from 'node:fs';
import { createHash } from 'node:crypto';
import {
    readFile, stat, mkdir, writeFile,
} from 'node:fs/promises';

import {
    pgClients, getMeta, metaFormat,
} from '@opengis/fastify-table/utils.js';

import rootFolder from '../../../plugins/mapnik/funcs/rootFolder.mjs';

const systemColumns = ['uid', 'cdate', 'editor_id', 'editor_date', 'created_by', 'created_at', 'updated_by', 'updated_at', 'geom'];

/**
 * Повертає JSON дані для реєстру на основі налаштувань з gis.registers
 * 
 * @method GET
 * @param {string} params.layer - register_id
 * @param {string} query.page - номер сторінки (default: 1)
 * @param {string} query.limit - кількість записів на сторінку (default: 100)
 * @param {string} query.where - додаткова WHERE умова
 * @param {string} query.sql - повернути SQL запит замість даних
 * @param {boolean} query.nocache - не використовувати кеш
 */
export default async function jsonData({
    pg = pgClients.client, params = {}, query = {}, unittest,
}, reply) {
    const { layer } = params;
    const {
        page = 1,
        limit = 100,
        where,
        sql,
        nocache
    } = query;

    if (!layer) {
        return reply.status(400).send('not enough params: layer (register_id)');
    }

    const registerId = layer.replace('.json', '');
    const pageNum = parseInt(page, 10) || 1;
    const limitNum = Math.min(parseInt(limit, 10) || 100, 1000); // max 1000
    const offset = (pageNum - 1) * limitNum;

    if (!pg.pk?.['gis.registers']) {
        return reply.status(404).send('registers table not found');
    }

    const register = await pg.query(
        `select 
            table_name,
            pk,
            query,
            columns,
            is_public,
            is_active,
            "order"
        from gis.registers 
        where register_id = $1 and is_active = true and is_public = true`,
        [registerId],
    ).then(el => el.rows?.[0]);

    if (!register) {
        return reply.status(404).send('register not found or not available');
    }

    const {
        table_name: table,
        pk,
        query: registerQuery,
        columns,
        order: orderBy,
    } = register;

    if (!table) {
        return reply.status(400).send('invalid register table_name');
    }

    if (!pg.pk?.[table]?.length) {
        return reply.status(404).send('table not found');
    }

    const meta = await getMeta({ pg, table });
    const { columns: metaColumns = [] } = meta || {};

    let attributeColumns = [];
    if (columns && Array.isArray(columns)) {
        attributeColumns = columns
            .filter(col => col.name && !systemColumns.includes(col.name))
            .map(col => col.name);
    }

    const columnsFiltered = attributeColumns.length > 0
        ? metaColumns.filter(col => attributeColumns.includes(col.name) && !systemColumns.includes(col.name))
        : metaColumns.filter(col => !systemColumns.includes(col.name));

    const props = columnsFiltered.map(el =>
        `"${el.name}"${['int4'].includes(el.type) ? '::text' : ''}`
    ).join(', ');

    if (!props) {
        return reply.status(400).send('no columns to select');
    }

    const cacheHash = createHash('md5')
        .update([registerId, where, registerQuery, pageNum, limitNum].filter(el => el).join('-'))
        .digest('hex');

    const today = new Date().toISOString().split('T')[0];
    const filepath = path.join(
        rootFolder,
        `/json/${registerId}/${today}/${cacheHash}.json`,
    );

    if (existsSync(filepath) && !nocache && !sql && !unittest) {
        const { birthtimeMs = Date.now() } = await stat(filepath);
        const ageInMs = Date.now() - birthtimeMs;
        if (ageInMs < 86400000) {
            const content = await readFile(filepath, 'utf8');
            return reply
                .status(200)
                .header('Content-Type', 'application/json')
                .header('Cache-Control', 'public, max-age=86400')
                .send(content);
        }
    }

    const whereConditions = [
        registerQuery || '1=1',
        where || 'true',
    ].join(' AND ');

    const quotedTable = table.split('.').map(el => `"${el}"`).join('.');

    const countQuery = `
        SELECT COUNT(*) as total
        FROM ${quotedTable}
        WHERE ${whereConditions}
    `;

    const dataQuery = `
        SELECT "${pk}" as id ${props ? ', ' + props : ''}
        FROM ${quotedTable}
        WHERE ${whereConditions}
        ${orderBy ? `ORDER BY ${orderBy}` : ''}
        LIMIT ${limitNum}
        OFFSET ${offset}
    `;

    if (sql === '1') return { countQuery, dataQuery };
    if (sql === '2') return filepath.replace(/\\/g, '/');

    try {
        const [countResult, dataResult] = await Promise.all([
            pg.query(countQuery),
            pg.query(dataQuery),
        ]);

        const total = parseInt(countResult.rows[0]?.total || 0, 10);
        const rows = dataResult.rows || [];

        // Обробка класифікаторів
        if (columns && Array.isArray(columns)) {
            const classifiers = columns
                .filter(col => col.data && ['select', 'badge', 'tags', 'integer', 'text', 'text[]'].includes(col.format))
                .reduce((acc, curr) => ({ ...acc, [curr.name]: curr.data }), {});

            if (Object.keys(classifiers).length > 0) {
                await metaFormat({
                    rows,
                    table,
                    cls: classifiers,
                    suffix: true,
                });
            }
        }

        const result = {
            register_id: registerId,
            total,
            page: pageNum,
            limit: limitNum,
            pages: Math.ceil(total / limitNum),
            data: rows,
        };

        const jsonString = JSON.stringify(result);

        if (!nocache && !unittest) {
            await mkdir(path.dirname(filepath), { recursive: true });
            await writeFile(filepath, jsonString, 'utf8');
        }

        return reply
            .status(200)
            .header('Content-Type', 'application/json')
            .header('Cache-Control', nocache ? 'no-cache' : 'public, max-age=86400')
            .send(jsonString);
    } catch (err) {
        return reply.status(500).send({ error: err.message });
    }
}
