'use strict';

const { sql } = require('../../db');
const { db } = require('../../utils/dbName');
const { inspectTables, requireColumns } = require('../softrestaurantIntrospection');

function hasColumn(metadata, tableName, columnName) {
  return Boolean(metadata.tables?.[tableName]?.columns?.[columnName]);
}

function hasTable(metadata, tableName) {
  return Boolean(metadata.tables?.[tableName]?.exists);
}

function column(metadata, tableName, alias, columnName, fallback = 'NULL') {
  return hasColumn(metadata, tableName, columnName) ? `${alias}.${columnName}` : fallback;
}

function sourceUpdatedAt(metadata, tableName, alias) {
  return hasColumn(metadata, tableName, 'fechacancelado')
    ? `COALESCE(${alias}.fechacancelado, ${alias}.fecha)`
    : `${alias}.fecha`;
}

function missingRequired(metadata) {
  return [
    ...requireColumns(metadata, 'cheques', ['folio', 'fecha', 'cancelado', 'pagado', 'total']),
    ...requireColumns(metadata, 'cheqdet', ['foliodet', 'idproducto', 'cantidad', 'precio']),
  ];
}

function productSelect(metadata, detailAlias) {
  if (!hasTable(metadata, 'productos')) return `CAST(${detailAlias}.idproducto AS nvarchar(80))`;
  if (!hasColumn(metadata, 'productos', 'descripcion')) return `CAST(${detailAlias}.idproducto AS nvarchar(80))`;
  return `LTRIM(RTRIM(ISNULL(p.descripcion, CAST(${detailAlias}.idproducto AS nvarchar(80)))))`;
}

function groupSelect(metadata) {
  if (!hasTable(metadata, 'productos') || !hasTable(metadata, 'grupos')) return `'Sin grupo'`;
  if (!hasColumn(metadata, 'productos', 'idgrupo') || !hasColumn(metadata, 'grupos', 'idgrupo') || !hasColumn(metadata, 'grupos', 'descripcion')) return `'Sin grupo'`;
  return "LTRIM(RTRIM(ISNULL(g.descripcion, 'Sin grupo')))";
}

function costSelect(metadata, detailAlias) {
  const ready = hasTable(metadata, 'costos')
    && hasTable(metadata, 'insumosdetalle')
    && hasColumn(metadata, 'costos', 'idproducto')
    && hasColumn(metadata, 'costos', 'idinsumo')
    && hasColumn(metadata, 'costos', 'cantidad')
    && hasColumn(metadata, 'insumosdetalle', 'idinsumo');
  if (!ready) return '0';
  const hasAverageCost = hasColumn(metadata, 'insumosdetalle', 'costopromedio');
  const hasCost = hasColumn(metadata, 'insumosdetalle', 'costo');
  const unitCost = hasAverageCost && hasCost
    ? 'COALESCE(idet.costopromedio, idet.costo, 0)'
    : hasAverageCost ? 'ISNULL(idet.costopromedio, 0)'
      : hasCost ? 'ISNULL(idet.costo, 0)' : '0';
  return `(SELECT ISNULL(SUM(ISNULL(cx.cantidad, 0) * ${unitCost}), 0)
    FROM ${db()}costos cx
    LEFT JOIN ${db()}insumosdetalle idet ON idet.idinsumo = cx.idinsumo
    WHERE cx.idproducto = ${detailAlias}.idproducto)`;
}

function productJoins(metadata) {
  const joins = [];
  if (hasTable(metadata, 'productos')) joins.push(`LEFT JOIN ${db()}productos p ON p.idproducto = cd.idproducto`);
  if (hasTable(metadata, 'productos') && hasTable(metadata, 'grupos') && hasColumn(metadata, 'productos', 'idgrupo') && hasColumn(metadata, 'grupos', 'idgrupo')) {
    joins.push(`LEFT JOIN ${db()}grupos g ON p.idgrupo = g.idgrupo`);
  }
  return joins.join('\n        ');
}

function tempProductJoins(metadata) {
  const joins = [];
  if (hasTable(metadata, 'productos')) joins.push(`LEFT JOIN ${db()}productos p ON p.idproducto = td.idproducto`);
  if (hasTable(metadata, 'productos') && hasTable(metadata, 'grupos') && hasColumn(metadata, 'productos', 'idgrupo') && hasColumn(metadata, 'grupos', 'idgrupo')) {
    joins.push(`LEFT JOIN ${db()}grupos g ON p.idgrupo = g.idgrupo`);
  }
  return joins.join('\n        ');
}

function canUseTemp(metadata) {
  if (!hasTable(metadata, 'tempcheques') || !hasTable(metadata, 'tempcheqdet')) return false;
  return ![
    ...requireColumns(metadata, 'tempcheques', ['folio', 'fecha']),
    ...requireColumns(metadata, 'tempcheqdet', ['foliodet', 'idproducto', 'cantidad', 'precio']),
  ].length;
}

function normalizeGroupKey(value) {
  if (value === null || value === undefined) return null;
  const key = String(value).trim();
  return key && key !== '0' ? key : null;
}

function normalizeSaleItemRow(row) {
  return normalizeSaleItemRows([row])[0];
}

function compoundGroupIdentity(row, groupKey) {
  if (!groupKey) return null;
  return [
    row.origen || '',
    row.business_date ? String(row.business_date).slice(0, 10) : '',
    row.foliodet || row.folio || '',
    groupKey,
  ].join(':');
}

function normalizeSaleItemRows(rows = []) {
  const parentByGroup = new Map();
  for (const row of rows) {
    const groupKey = normalizeGroupKey(row.idproductocompuesto);
    const groupIdentity = compoundGroupIdentity(row, groupKey);
    const isParent = Number(row.productocompuestoprincipal || 0) === 1;
    if (groupIdentity && isParent && !parentByGroup.has(groupIdentity)) {
      parentByGroup.set(groupIdentity, row);
    }
  }
  return rows.map((row) => normalizeSaleItemRowWithParents(row, parentByGroup));
}

function normalizeSaleItemRowWithParents(row, parentByGroup = new Map()) {
  const groupKey = normalizeGroupKey(row.idproductocompuesto);
  const groupIdentity = compoundGroupIdentity(row, groupKey);
  const parent = groupIdentity ? parentByGroup.get(groupIdentity) : null;
  const isParent = Number(row.productocompuestoprincipal || 0) === 1;
  const isModifier = Number(row.modificador || 0) === 1;
  const isComponent = Boolean(groupKey && !isParent);
  const total = Number(row.total || 0);
  const price = Number(row.precio || 0);
  const contributionTotal = groupKey
    ? (isParent || (!isModifier && price !== 0) || (isModifier && price !== 0) ? total : 0)
    : total;
  const saleItemGroup = groupKey ? {
    group_key: groupKey,
    compound_group_key: groupIdentity,
    is_parent: isParent,
    is_component: isComponent,
    is_modifier: isModifier,
    parent_product_id: isParent ? row.idproducto : parent?.idproducto || null,
    parent_line_id: isParent ? row.external_id || null : parent?.external_id || null,
    total_contribution: contributionTotal,
  } : null;
  const normalized = {
    ...row,
    group_key: groupKey,
    compound_group_key: groupIdentity,
    is_composite_parent: isParent,
    is_compound_parent: isParent,
    is_composite_component: isComponent,
    is_modifier: isModifier,
    parent_product_id: saleItemGroup?.parent_product_id || null,
    parent_line_id: saleItemGroup?.parent_line_id || null,
    total_contribution: contributionTotal,
    sale_item_group: saleItemGroup,
  };
  return { ...normalized, payload: { ...normalized, raw_payload: row } };
}

async function inspect(pool) {
  return inspectTables(pool, {
    cheques: ['folio', 'fecha', 'fechacancelado', 'mesa', 'idmesero', 'idturno', 'total', 'cancelado', 'pagado'],
    cheqdet: ['foliodet', 'idproducto', 'idproductocompuesto', 'modificador', 'productocompuestoprincipal', 'cantidad', 'precio'],
    tempcheques: ['folio', 'fecha', 'idmesero', 'idturno', 'total', 'cancelado', 'pagado'],
    tempcheqdet: ['foliodet', 'idproducto', 'idproductocompuesto', 'modificador', 'productocompuestoprincipal', 'cantidad', 'precio'],
    productos: ['idproducto', 'descripcion', 'idgrupo'],
    grupos: ['idgrupo', 'descripcion'],
    costos: ['idproducto', 'idinsumo', 'cantidad'],
    insumosdetalle: ['idinsumo', 'costo', 'costopromedio'],
  });
}

async function extract(pool, watermark, limit) {
  const metadata = await inspect(pool);
  const errors = missingRequired(metadata);
  if (errors.length) throw new Error(`SoftRestaurant sale_items schema incompleto: ${errors.join('; ')}`);
  const unions = [`
        SELECT
          'C' AS origen,
          c.folio,
          c.fecha AS source_created_at,
          ${sourceUpdatedAt(metadata, 'cheques', 'c')} AS source_updated_at,
          CAST(c.fecha AS date) AS business_date,
          cd.foliodet,
          ${column(metadata, 'cheques', 'c', 'idturno')} AS idturno,
          ${column(metadata, 'cheques', 'c', 'idmesero')} AS idmesero,
          cd.idproducto,
          ${column(metadata, 'cheqdet', 'cd', 'idproductocompuesto')} AS idproductocompuesto,
          ${column(metadata, 'cheqdet', 'cd', 'modificador', '0')} AS modificador,
          ${column(metadata, 'cheqdet', 'cd', 'productocompuestoprincipal', '0')} AS productocompuestoprincipal,
          ${productSelect(metadata, 'cd')} AS producto,
          ${groupSelect(metadata)} AS grupo,
          ISNULL(cd.cantidad, 0) AS cantidad,
          ISNULL(cd.precio, 0) AS precio,
          ISNULL(cd.cantidad, 0) * ISNULL(cd.precio, 0) AS total,
          ${costSelect(metadata, 'cd')} AS costo_unitario
        FROM ${db()}cheques c
        INNER JOIN ${db()}cheqdet cd ON cd.foliodet = c.folio
        ${productJoins(metadata)}
        WHERE c.cancelado = 0
          AND c.pagado = 1
          AND c.total > 0
          AND ${sourceUpdatedAt(metadata, 'cheques', 'c')} > @last
          AND ISNULL(cd.cantidad, 0) <> 0`];
  if (canUseTemp(metadata)) {
    unions.push(`
        SELECT
          'T' AS origen,
          tc.folio,
          tc.fecha AS source_created_at,
          tc.fecha AS source_updated_at,
          CAST(tc.fecha AS date) AS business_date,
          td.foliodet,
          ${column(metadata, 'tempcheques', 'tc', 'idturno')} AS idturno,
          ${column(metadata, 'tempcheques', 'tc', 'idmesero')} AS idmesero,
          td.idproducto,
          ${column(metadata, 'tempcheqdet', 'td', 'idproductocompuesto')} AS idproductocompuesto,
          ${column(metadata, 'tempcheqdet', 'td', 'modificador', '0')} AS modificador,
          ${column(metadata, 'tempcheqdet', 'td', 'productocompuestoprincipal', '0')} AS productocompuestoprincipal,
          ${productSelect(metadata, 'td')} AS producto,
          ${groupSelect(metadata)} AS grupo,
          ISNULL(td.cantidad, 0) AS cantidad,
          ISNULL(td.precio, 0) AS precio,
          ISNULL(td.cantidad, 0) * ISNULL(td.precio, 0) AS total,
          ${costSelect(metadata, 'td')} AS costo_unitario
        FROM ${db()}tempcheques tc
        INNER JOIN ${db()}tempcheqdet td ON td.foliodet = tc.folio
        ${tempProductJoins(metadata)}
        WHERE ISNULL(${column(metadata, 'tempcheques', 'tc', 'cancelado', '0')}, 0) = 0
          AND ISNULL(${column(metadata, 'tempcheques', 'tc', 'pagado', '1')}, 0) = 1
          AND ISNULL(${column(metadata, 'tempcheques', 'tc', 'total', '1')}, 0) > 0
          AND tc.fecha > @last
          AND ISNULL(td.cantidad, 0) <> 0
          AND NOT EXISTS (
            SELECT 1
            FROM ${db()}cheques ch
            WHERE ch.folio = tc.folio
              AND CAST(ch.fecha AS date) = CAST(tc.fecha AS date)
              AND ch.cancelado = 0
              AND ch.pagado = 1
          )`);
  }
  const result = await pool.request()
    .input('last', sql.DateTime, watermark ? new Date(watermark) : new Date('2000-01-01T00:00:00'))
    .input('limit', sql.Int, limit)
    .query(`
      SELECT TOP (@limit)
        CAST(CONCAT(m.origen, '-', CONVERT(varchar(10), m.business_date, 120), '-', m.folio, '-', COALESCE(NULLIF(CAST(m.idproductocompuesto AS varchar(80)), ''), CAST(m.idproducto AS varchar(80))), '-', ROW_NUMBER() OVER (PARTITION BY m.origen, m.business_date, m.folio ORDER BY m.idproductocompuesto, m.productocompuestoprincipal DESC, m.modificador, m.idproducto, m.precio, m.cantidad)) AS varchar(180)) AS external_id,
        m.source_created_at,
        m.source_updated_at,
        m.business_date,
        m.origen,
        m.folio,
        m.foliodet,
        m.idturno,
        m.idmesero,
        m.idproducto,
        m.producto,
        m.grupo,
        m.idproductocompuesto,
        m.modificador,
        m.productocompuestoprincipal,
        m.cantidad,
        m.precio,
        m.total,
        m.costo_unitario,
        m.cantidad * m.costo_unitario AS costo_total
      FROM (
${unions.join('\n        UNION ALL\n')}
      ) m
      WHERE m.source_updated_at > @last
      ORDER BY m.source_updated_at, m.folio, m.idproductocompuesto, m.productocompuestoprincipal DESC, m.idproducto
    `);
  return normalizeSaleItemRows(result.recordset);
}

module.exports = { entityType: 'sale_items', extract, normalizeSaleItemRow, normalizeSaleItemRows, normalizeGroupKey, inspect };
