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

const layerMetaColumns = ['id', 'name', 'label', 'source', 'table', 'pk', 'geomColumn', 'srid', 'cls', 'htmls'];

export default async function mapFeaturesPoint({ pg = pgClients.client, params = {}, query = {} }, reply) {
    const { slug, id } = params;
    const { x, y, sql } = query;

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

    if (!(x && y) && !id) {
        return reply.status(400).send('not enough params: xy');
    }

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

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

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

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

    const serviceLayers = await Promise.all(layers1.filter(el => typeof el === 'string').map(async (layer) => {
        const layerData = await getTemplate('layer', layer);
        const serviceData = layerData || await pg.query('select service_id as id, *, st_asgeojson(bbox)::json as bbox, st_asgeojson(center)::json as center from gis.services where service_id=$1', [layer]).then(el => el.rows?.[0]);
        serviceData.style = yml2json(serviceData.style);
        return { ...serviceData, id: layer, visible: true };
    }));

    const layers = layers1.filter(el => el && typeof el === 'object').concat(serviceLayers);

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

    if (!layers.filter(el => el.source_path).length) {
        return reply.status(404).send('map layers sources not provided');
    }

    const point = `srid=4326;point(${x} ${y})`; // 30, 50

    const { srids } = await pg.queryCache?.('select json_agg(srid) as srids from spatial_ref_sys').then(el => el.rows?.[0] || {}) || {};

    await Promise.all(layers.map(async (layer, idx) => {
        const { source_path: source, srid = 4326, id: layerId, name } = layer;

        const loadTable = await getTemplate('table', source);
        const meta = await getMeta({ pg, table: loadTable?.table || source });
        const { geom = 'geom' } = meta || {};
        const { table = source, columns = meta?.columns } = loadTable || {};

        const cls = columns
            ?.filter(el => el.name && el.data)
            ?.reduce?.((acc, curr) => ({ ...acc, [curr.name]: curr.data }), {});
        const htmls = columns
            ?.filter(el => el.name && el.html)
            ?.reduce?.((acc, curr) => ({ ...acc, [curr.name]: curr.html }), {});

        const pk = pg.pk?.[table] || loadTable?.key;

        const { optimizedSQL } = await getFilterSQL({ pg, table: source, query: layer?.query });

        Object.assign(layer, {
            layer: (layerId || name || idx.toString())?.replace?.(/'/g, "''"),
            table,
            pk,
            cls,
            htmls,
            geomColumn: geom,
            optimizedSQL,
            srid,
            step: srids?.includes?.(srid - 0) && srid !== 4326 ? 10 : 0.0002,
        });
    }));

    const unique = layers
        .filter((el, idx, arr) => el.table && arr.map((item) => item.table).indexOf(el.table) === idx);

    const q = unique.map((el) => {
        const where = id
            ? `${el.pk}::text='${id.replace(/'/g, "''")}'`
            : `case
                    when ST_GeometryType(${el.geomColumn}) in ('ST_Polygon','ST_MultiPolygon')
                    then st_intersects(${el.geomColumn},st_buffer('${point.replace(/'/g, "''")}',${el.step}))
                
                    when ST_GeometryType(${el.geomColumn}) in ('ST_Line','ST_MultiLineString', 'ST_MultiPoint', 'ST_Point')
                    then st_distance(${el.geomColumn},'${point.replace(/'/g, "''")}') < ${el.step}
                    else false
                end`;
        return `select ${el.pk}::text as pk, '${el.layer}' as layer,
    row_to_json(t) as data, 
    ${id ? 'null' : `st_distance(${el.geomColumn},'${point.replace(/'/g, "''")}')`} as distance,
    st_asgeojson(${el.geomColumn})::json as geom from (${el.optimizedSQL}) t 
    where ${where}`;
    }).join(' union all ').concat(' order by distance');

    if (sql === '1') return q;

    const { rows: objects = [] } = await pg.query(q);

    if (!objects.length) {
        return reply.status(200).send(`object not found: ${id}`);
    }

    const layerData = layers.find((el) => (el.id || el.name) === objects[0].layer) || {};
    const rows = objects.slice(0, 1).map(el => ({ ...el.data, data: undefined }));

    const { cls, htmls } = layerData;
    await metaFormat({ rows, cls, htmls, sufix: false }, pg);

    const resp = {
        meta: {
            ...layerMetaColumns.reduce((acc, curr) => ({ ...acc, [curr]: layerData[curr] }), {}),
            distance: objects[0].distance,
        },
        data: {
            id: rows[0][layerData.pk],
            ...rows[0],
        },
    };

    return reply.status(200).send(resp);
}