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

const columnType = {
  text: 'text',
  date: 'date',
  bool: 'yes/no',
  numeric: 'number',
  integer: 'number',
  'timestamp without time zone': 'date',
  'timestamp with time zone': 'date',
};

export default async function mapInfoPoint(req, reply) {
  const { pg = pgClients.client, query = {} } = req;
  const {
    x, y, layers, index = 0,
  } = query;
  const time = Date.now();

  if (!x || !y || !layers) {
    return reply.status(400).send('not enough query params: x, y, layers are required');
  }

  const rows = pg?.pk?.['gis.ogc_service'] ? await pg.query('select * from gis.ogc_service WHERE enabled and table_name is not null and ogc_service_id = any($1)', [layers.split(',')]).then(el => el.rows || []) : [];

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

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

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

  await Promise.all(rows.map(async (layer, idx) => {
    const { table_name: source, ogc_service_id: layerId } = 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 { srid, x } = await pg.query(`select st_srid(${geom}) as srid, st_x(st_pointonsurface(${geom})) as x from ${table} where geom is not null limit 1`).then(el => el.rows?.[0] || {});

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

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

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

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

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

  const qList = unique.map((el) => ({
    query: `select ${el.pk}::text as pk, '${el.layer}' as layer, '${el.table}' as table,
    row_to_json(t) as data, 
    st_distance(${el.geomColumn},'${point.replace(/'/g, "''")}') as distance,
    st_asgeojson(${el.geomColumn})::json as geom from (${el.optimizedSQL}) t 
    where 1=1`,
    queryCount: `select 1 from (${el.optimizedSQL})t where 1=1`,
    /* where: `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`, */
    where: `${el.geomColumn} && '${point.replace(/'/g, "''")}'`,
  }));

  const q = qList.map(el => `${el.query} and ${el.where}`).join(' union all ').concat(` order by distance limit 1 offset ${Math.max(+index || 0, 0)}`);

  const qCount = qList.map(el => `${el.queryCount} and ${el.where}`).join(' union all ');

  if (query.sql === '1') return q;
  if (query.sql === '2') return qCount;

  const count = await pg.query(qCount).then(el => el.rowCount);

  const row = await pg.query(q).then(el => el.rows?.[0]);

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

  const { columns = [] } = await getMeta({ pg, table: row.table }) || {};
  const fields = columns.map(({ name, dataTypeID, title }) => ({ name, type: columnType[pg.pgType?.[dataTypeID] || 'text'], label: title || name }));

  const htmlTemplate = pg?.pk?.['admin.templates'] && row.layer && false
    ? await pg.query('select body from admin.templates where name=$1 limit 1', [row.layer]).then(el => el.rows?.[0]?.body)
    : rows.find(el => el.ogc_service_id === row.layer)?.html;

  const html = await handlebars.compile(htmlTemplate || 'template not found')(row.data);

  const res = {
    time: Date.now() - time,
    index,
    count,
    data: {
      layer: row.layer,
      geom: row.geom,
      id: row.pk,
      distance: row.distance,
      data: row.data,
    },
    fields,
    html,
  };

  return res;
}
