import path from 'node:path';
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
import { fileURLToPath } from 'url';
import { createHash } from 'node:crypto';

import { config, getMeta, getTemplate, getTemplateSync, pgClients, getSelect, getSelectVal, getTemplatePath } from '@opengis/fastify-table/utils.js';

const { prefix = '/api' } = config;
const dirname = path.dirname(fileURLToPath(import.meta.url));

const maps = getTemplatePath('map')?.map?.(el => {
    const { name = el[0] } = getTemplateSync('map', el[0]) || {};
    return { slug: el[0], name };
});

async function processObject(loadTemplate, pg = pgClients.client, unittest) {
    // default center for all maps (frontend dependends on it)
    const { bbox = [], center = [30, 50] } = loadTemplate?.source_path && pg.pk?.[loadTemplate.source_path]
        ? await pg.query(`select
                array[ST_XMin(env), ST_YMin(env), ST_XMax(env),ST_YMax(env)] as bbox,
                st_asgeojson(st_centroid(env))::json->'coordinates' as center
            from ( select st_extent(geom) as env from ${loadTemplate.source_path} where ${loadTemplate.source_query || '1=1'})q`
        ).then(el => el.rows?.[0] || {})
        : {};
    loadTemplate.map = { bbox, center };


    if (!loadTemplate?.widgets?.length) return;
    // add legend if not exists in widget
    const idx = loadTemplate.widgets.findIndex(el => el.type === 'attribute_style');
    const { source_path: attrTable, attribute, cls: clsName, legend = [], colors } = loadTemplate.widgets.find(el => el.type === 'attribute_style')?.config || {};
    const colors1 = colors || ['red', 'black', 'blue', 'green', 'orange'];

    if (!legend?.length && attribute && attrTable) {
        const { columns = {} } = await getMeta({ pg, table: attrTable });
        const { dataTypeID = 0 } = columns.find(item => item.name === attribute) || {};
        const q = pg.pgType?.[dataTypeID]?.includes?.('[]')
            ? `select unnest(${attribute})::text as id,count(*) from ${attrTable} group by unnest(${attribute}) limit 100`
            : `select ${attribute}::text as id,count(*) from ${attrTable} group by ${attribute} limit 100`;

        if (config.trace || unittest) console.log(q);
        const countArr = await pg.queryCache(q, { time: 5 });

        const ids = countArr.rows.map(item => item.id);

        const cls = await getSelect(clsName, pg);
        const clsData = await getSelectVal({ pg, values: ids, cls: clsName });

        const options = countArr.rows.map((cel, idx) => {
            const data = cls?.arr?.find?.(c => c.id?.toString?.() === cel.id?.toString?.()) || { text: clsData?.[cel.id]?.text || clsData?.[cel.id] };
            const color = (colors?.length ? colors1[idx % colors1.length] : null) || data?.color || colors1[idx % colors1.length];
            return { ...cel, ...data, color };
        });
        loadTemplate.widgets[idx].config.legend = options;
    }


    const { source_path: table, filters = [] } = loadTemplate.widgets.find(el => el.type === 'filters')?.config || {};

    filters.forEach(el => Object.assign(el, { name: el.name || el.id }));

    // md5 hash for suggest security
    filters.filter(el => el.type?.toLowerCase() === 'select').map(async (el) => {
        const hash = createHash('md5').update([table, el.name].join()).digest('hex');
        el.api = `${prefix}/suggest/hash-${hash}`;
        const filepath = path.join(dirname, `../../../../log/suggest/${hash}.json`);
        if (existsSync(filepath)) { return; };
        mkdirSync(path.dirname(filepath), { recursive: true });
        writeFileSync(filepath, JSON.stringify({ table, column: el.name }), { encoding: 'utf8' });
    });

    // add options to filters
    if (filters.filter(el => el.data).length && table) {
        const { columns = {} } = await getMeta({ pg, table });
        await Promise.all(filters.filter(el => el.data && el.name).map(async (el) => {
            const { dataTypeID = 0 } = columns.find(item => item.name === el.name) || {};
            const q = pg.pgType?.[dataTypeID]?.includes?.('[]')
                ? `select unnest(${el.name})::text as id,count(*) from ${table} group by unnest(${el.name}) limit 100`
                : `select ${el.name}::text as id,count(*) from ${table} group by ${el.name} limit 100`;

            if (config.trace || unittest) console.log(q);
            const countArr = await pg.queryCache(q, { time: 5 });

            const ids = countArr.rows.map(item => item.id);

            const cls = await getSelect(el.data, pg);
            const clsData = await getSelectVal({ pg, values: ids, name: el.data });

            const options = countArr.rows.map(cel => {
                const data = cls?.arr?.find?.(c => c.id?.toString?.() === cel.id?.toString?.()) || { text: clsData[cel.id]?.text || clsData[cel.id] };
                return { ...cel, ...data };
            });
            Object.assign(el, { options });
        }));
    }
    return loadTemplate;
}

export default async function map({ pg = pgClients.client, params = {}, unittest }, reply) {
    const { slug } = params;

    const loadTemplate = await getTemplate('map', slug);

    if (!loadTemplate) {
        return reply.status(404).send('map not found');
    }

    if (!Array.isArray(loadTemplate)) {
        await processObject(loadTemplate, pg, unittest);
        return { ...loadTemplate, maps };
    }

    const index = loadTemplate?.find?.(el => el[0].split('.').shift() === 'index')?.[1];

    if (!index) {
        return reply.status(404).send('map index not found');
    }

    await processObject(index, pg, unittest);

    const layers = loadTemplate?.filter?.(el => el[0].split('.').shift() !== 'index')?.map(el => ({ ...el[1], id: el[0].replace('.yml', '').replace('.json', '') }));

    return reply.status(200).send({ ...index, layers, maps });
}