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

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

function getLayerTableQuery({
  el, id, point, nogeom, radius,
}, pg = pgClients.client) {
  const {
    gcol: geom = 'geom', table, pk = pg.pk[el.table], query: tableQuery, srid, columns,
  } = el;
  const buffer = `ST_Buffer('${point}'::geography, ${radius || 50})::geometry`;
  const q = `SELECT
  ${pk} as "id"
  ,'${el.key}' as "key"
  ${nogeom ? '' : `,st_asgeojson(${srid !== 4326 ? `st_transform(${geom}, 4326)` : geom})::json as geom`}, ${srid !== 4326 ? `st_transform(${geom}, 4326)` : geom}::box2d as box2d, ${columns ? `row_to_json(t)` : 'null'} as "data" 
  ${point ? `,st_distance(${el.srid !== 4326 ? `st_transform(${geom}, 4326)` : geom},'${point} ') as distance` : ''} 
  FROM ${table} t WHERE ${tableQuery || '1 = 1'} and st_isvalid(${geom}) and st_srid(${geom}) > 0
  and ${point
      ? `case when ST_GeometryType(${geom}) in ('ST_Polygon','ST_MultiPolygon', 'ST_LineString','ST_MultiLineString', 'ST_MultiPoint', 'ST_Point') then st_intersects(${srid !== 4326 ? `st_transform(${geom}, 4326)` : geom}, ${buffer}) else false end`
      : '1=1'}
  and ${id ? `${pk}='${id.replace(/'/g, "")}'` : '2=2'} 
  ${point ? 'order by distance' : ''} limit 10`;
  return q;
}

async function getLayersData({
  id, lng, lat, cartocss, nogeom, radius,
}, pg = pgClients.client) {
  if (!(lng && lat) && !id) {
    return { error: 'not enough params: lat / lng' };
  }

  if (!cartocss.config?.length && !cartocss.card_table) {
    return { error: 'empty cartocss.config' };
  }

  const point = lng && lat ? `srid=4326;point(${lng} ${lat})`.replace(/'/g, "''") : null;

  const q = cartocss.card_table
    ? getLayerTableQuery({
      el: { key: cartocss.card_table, table: cartocss.card_table, columns: cartocss.card_columns }, id, point, nogeom, radius,
    }, pg)
    : cartocss.config.filter((el, idx, arr) => el.key && el.table && pg.pk?.[el.table] && arr.map(item => item.table).indexOf(el.table) === idx).map(el => getLayerTableQuery({
      el, id, point, nogeom, radius,
    }, pg)).join(' union all ');

  // console.time(q);
  const rows = await pg.query(q).then(el => el.rows || []);
  // console.timeEnd(q);

  if (!rows.length) {
    return { error: 'object not found', code: 404 };
  }

  const layerData = rows[0].key ? cartocss.config?.find(el => el.key === rows[0].key) || { table: cartocss?.card_table, columns: cartocss?.card_columns } : {};

  const { data } = rows[0] || {};

  if (rows[0].data && layerData.columns) {
    const columnNames = layerData.columns.split(',');
    const rowData = Object.keys(rows[0].data).filter(key => columnNames.includes(key)).reduce((acc, key) => ({ ...acc, [key]: rows[0].data[key] }), {});
    Object.assign(rows[0], { data: undefined, ...rowData });
  }

  const metaColumns = cartocss?.card_table
    ? await getMeta({ pg, table: cartocss?.card_table }).then(el => el.columns?.map(({ name }) => ({ name, format: 'text' })) || [])
    : [];

  const columns = metaColumns.length && cartocss?.card_columns
    ? metaColumns.filter(el => cartocss?.card_columns.includes(el.name))
    : Object.keys(rows[0] || {}).map(key => ({ name: key, format: 'text' }));

  return {
    ids: rows.map(row => row.id),
    rows,
    data,
    columns,
    tname: layerData.table,
    htmlTemplate: cartocss.card_html,
  };
}

async function getTableData({
  service, layer, id, user, nogeom,
}, pg = pgClients.client) {
  const {
    source_path: tname, card, template,
  } = service || {};

  const columns = (card || []).filter((col) => (user?.uid ? true : !col.is_admin));

  if (!tname) {
    return { error: 'missing source_path', code: 400 };
  }

  const pk = pg.pk?.[tname];

  if (!pk) {
    return { error: 'missing pk', code: 400 };
  }

  if (!columns) {
    return { error: 'columns setting not found', code: 404 };
  }

  const columnNames = columns.map(col => col.name);

  const filteredColumns = columnNames.filter(name => !excluded.includes(name));

  const rows = await pg.query(
    `SELECT ${pk} as "id", ${!nogeom ? 'st_asgeojson(geom)::json as geom, geom::box2d as box2d' : ''} ${!nogeom && filteredColumns?.length > 0 ? ',' : ''}  ${filteredColumns.map((n) => `"${n}"`).join(', ')} FROM ${tname} WHERE ${pk} = $1`,
    [id],
  ).then(el => el.rows || []);

  if (!rows.length) {
    return { error: 'object not found', code: 404 };
  }

  const classifiers = columns
    .filter(col => col.data && ['select', 'badge', 'tags'].includes(col.format))
    .reduce((acc, curr) => ({ ...acc, [curr.name]: curr.data }), {});

  const htmlTemplate = pg?.pk?.['admin.templates'] && service?.card_mode === 'html'
    ? await pg.query('select body from admin.templates where name=$1 limit 1', [template || layer]).then(el => el.rows?.[0]?.body)
    : null;

  return {
    rows,
    columns,
    classifiers,
    tname,
    htmlTemplate,
  };
}

export default async function mapFormat({ pg = pgClients.client, query = {}, user }, reply) {
  const {
    layer, id, lat, lng, nogeom, radius = 50,
  } = query;

  const t1 = Date.now();

  if (!layer) {
    return reply.status(400).send({ error: 'not enough query params: layer', code: 400 });
  }

  const service = await getTemplate('layer', layer) || (pg.pk?.['gis.services'] ? await pg.query('SELECT * FROM gis.services WHERE service_id = $1', [layer]).then(el => el.rows[0]) : null);
  const cartocss = pg.pk?.['gis.cartocss'] ? await pg.query('SELECT * FROM gis.cartocss WHERE cartocss_id = $1', [layer]).then(el => el.rows[0]) : null;

  if (!cartocss && !id) {
    return reply.status(400).send({ error: 'not enough query params: id', code: 400 });
  }

  if (cartocss && !(cartocss.config?.length || cartocss.card_table)) {
    return reply.status(400).send({ error: 'invalid layer settings: empty config', code: 400 });
  }

  if (cartocss && !(lat && lng) && !id) {
    return reply.status(400).send({ error: 'not enough query params: lat and lng / id', code: 400 });
  }

  const {
    ids, rows, data, columns, classifiers, tname, template, error, code = 500, htmlTemplate,
  } = cartocss
      ? await getLayersData({
        cartocss, id, lng, lat, radius, nogeom,
      }, pg)
      : await getTableData({
        service, layer, id, user, nogeom,
      }, pg);

  if (error) {
    return reply.status(code).send({ error, code });
  }

  rows.forEach(row => Object.assign(row, { box2d: undefined, bounds: row.box2d ? row.box2d.replace(/[A-Z\)\(]+/g, '').split(/[ ,]+/).map(el => el - 0) : null }));

  await metaFormat({
    rows, table: tname, cls: classifiers, sufix: false,
  });
  const fullRow = rows[0];

  const result = [];
  const filtered = columns.filter(col => !excluded.includes(col.name));
  for (let i = 0; i < filtered.length; i += 1) {
    const col = filtered[i];

    const name = col.label || col.ua || col.name;
    const value = (col.format === 'date' ? new Date(fullRow[col.name]).toLocaleDateString('uk-UA') : null) || fullRow[`${col.name} _data`]?.text
      || fullRow[`${col.name} _text`]
      || fullRow[col.name];

    result.push(`< div class="grid grid-cols-1 gap-1 py-3 sm:grid-cols-3 sm:gap-4 even:bg-gray-50 text-[12px]" >
            <dt class="text-gray-900">${name}</dt>
            <dd class="text-gray-700 sm:col-span-2">${value || '-'}</dd>
        </div > `);
  }

  const html1 = htmlTemplate ? await handlebars.compile(htmlTemplate)(data || fullRow) : null;

  const auto = service ? service?.card_mode === 'list' : cartocss?.card_auto;
  const html = html1 || (auto ? `< dl class="divide-y divide-gray-100 py-[5px]" > ${result.join('')}</dl > ` : undefined);

  const res = {
    time: Date.now() - t1,
    ids,
    id: fullRow.id,
    data: fullRow,
    columns,
    template,
    html,
    auto,
  };
  if (service) {
    res.cardInterface = service.cardInterface;
  }

  return res;
}
