'use strict';

const sql = require('mssql');
const { qname, inspectTables, requireColumns } = require('../softrestaurantIntrospection');
const { commandError } = require('../commandHandlers/schemas');
const { createMapper, schemaMismatch } = require('./baseMapper');

function asString(value, label, { required = false, max = 80 } = {}) {
  if (value === undefined || value === null || String(value).trim() === '') {
    if (required) throw commandError('invalid_payload', `${label} requerido`);
    return null;
  }
  return String(value).trim().slice(0, max);
}

function asNumber(value, label, { required = false, min = null } = {}) {
  if (value === undefined || value === null || value === '') {
    if (required) throw commandError('invalid_payload', `${label} requerido`);
    return null;
  }
  const number = Number(value);
  if (!Number.isFinite(number)) throw commandError('invalid_payload', `${label} debe ser numerico`);
  if (min !== null && number < min) throw commandError('invalid_payload', `${label} debe ser mayor o igual a ${min}`);
  return number;
}

function asBool(value) {
  if (value === undefined || value === null) return null;
  if (typeof value === 'boolean') return value;
  if (value === 1 || value === '1') return true;
  if (value === 0 || value === '0') return false;
  if (String(value).toLowerCase() === 'true') return true;
  if (String(value).toLowerCase() === 'false') return false;
  return Boolean(value);
}

async function validate(payload = {}, config = {}) {
  if (!payload || typeof payload !== 'object' || Array.isArray(payload)) throw commandError('invalid_payload', 'payload debe ser objeto');
  const productInput = payload.product || {};
  const detailInput = payload.detail || {};
  const configuredEmpresa = config.softrestaurantIdEmpresa || process.env.SOFTRESTAURANT_IDEMPRESA || null;
  const productEmpresa = asString(productInput.idempresa ?? detailInput.idempresa, 'idempresa');
  if (configuredEmpresa && productEmpresa && String(configuredEmpresa) !== String(productEmpresa)) {
    throw commandError('branch_idempresa_mismatch', 'idempresa del comando no coincide con la sucursal configurada');
  }
  const idempresa = productEmpresa || configuredEmpresa || null;
  const product = {
    idproducto: asString(productInput.idproducto ?? payload.idproducto ?? payload.product_id, 'product.idproducto', { required: true, max: 15 }),
    descripcion: asString(productInput.descripcion ?? productInput.name ?? payload.descripcion, 'product.descripcion', { required: true, max: 60 }),
    idgrupo: asString(productInput.idgrupo, 'product.idgrupo', { max: 5 }),
    nombrecorto: asString(productInput.nombrecorto, 'product.nombrecorto', { max: 20 }),
    precio: asNumber(productInput.precio ?? productInput.price, 'product.precio', { min: 0 }),
    idempresa,
  };
  const detail = {
    precio: asNumber(detailInput.precio ?? product.precio, 'detail.precio', { min: 0 }),
    bloqueado: asBool(detailInput.bloqueado),
    secuenciacompuesto: asBool(detailInput.secuenciacompuesto),
    finalizarsecuenciacompuesto: asBool(detailInput.finalizarsecuenciacompuesto),
    heredarmonitormodificadores: asBool(detailInput.heredarmonitormodificadores),
    usarmultiplicadorprodcomp: asBool(detailInput.usarmultiplicadorprodcomp),
    permitirprodcompenmodif: asBool(detailInput.permitirprodcompenmodif),
    ocultarmitades: asBool(detailInput.ocultarmitades),
  };
  const modifierGroups = payload.modifier_groups || payload.modifierGroups || [];
  if (!Array.isArray(modifierGroups)) throw commandError('invalid_payload', 'modifier_groups debe ser array');
  const seenGroups = new Set();
  const groups = modifierGroups.map((group, index) => {
    if (!group || typeof group !== 'object' || Array.isArray(group)) throw commandError('invalid_payload', `modifier_groups[${index}] debe ser objeto`);
    const idgruposmodificadores = asString(group.idgruposmodificadores ?? group.idgrupo_modificador, `modifier_groups[${index}].idgruposmodificadores`, { required: true, max: 5 });
    if (seenGroups.has(idgruposmodificadores)) throw commandError('duplicate_modifier_group', `Grupo modificador duplicado en payload: ${idgruposmodificadores}`);
    seenGroups.add(idgruposmodificadores);
    const assignmentInput = group.assignment || {};
    const assignmentEmpresa = asString(assignmentInput.idempresa ?? idempresa, `modifier_groups[${index}].assignment.idempresa`);
    if (configuredEmpresa && assignmentEmpresa && String(configuredEmpresa) !== String(assignmentEmpresa)) {
      throw commandError('branch_idempresa_mismatch', 'idempresa de grupo modificador no coincide con la sucursal configurada');
    }
    const seenModifiers = new Set();
    const modifiers = (group.modifiers || []).map((modifier, modifierIndex) => {
      const idproducto = asString(modifier.idproducto ?? modifier.product_id, `modifier_groups[${index}].modifiers[${modifierIndex}].idproducto`, { required: true, max: 15 });
      const modifierEmpresa = asString(modifier.idempresa ?? assignmentEmpresa ?? idempresa, `modifier_groups[${index}].modifiers[${modifierIndex}].idempresa`);
      if (configuredEmpresa && modifierEmpresa && String(configuredEmpresa) !== String(modifierEmpresa)) {
        throw commandError('branch_idempresa_mismatch', 'idempresa de modificador no coincide con la sucursal configurada');
      }
      const key = `${modifierEmpresa || ''}:${idproducto}`;
      if (seenModifiers.has(key)) throw commandError('duplicate_modifier', `Modificador duplicado en grupo ${idgruposmodificadores}: ${idproducto}`);
      seenModifiers.add(key);
      return {
        idproducto,
        descripcion: asString(modifier.descripcion ?? modifier.name, `modifier_groups[${index}].modifiers[${modifierIndex}].descripcion`, { max: 60 }),
        precio: asNumber(modifier.precio ?? modifier.price, `modifier_groups[${index}].modifiers[${modifierIndex}].precio`, { min: 0 }) ?? 0,
        idempresa: modifierEmpresa || idempresa,
      };
    });
    return {
      idgruposmodificadores,
      descripcion: asString(group.descripcion ?? group.name, `modifier_groups[${index}].descripcion`, { max: 30 }),
      assignment: {
        idempresa: assignmentEmpresa || idempresa,
        secuencia: asNumber(assignmentInput.secuencia ?? assignmentInput.prioridad, `modifier_groups[${index}].assignment.secuencia`, { min: 0 }),
        incluidos: asNumber(assignmentInput.incluidos ?? assignmentInput.incluidos_en_precio, `modifier_groups[${index}].assignment.incluidos`, { min: 0 }),
        maximomodificadores: asNumber(assignmentInput.maximomodificadores ?? assignmentInput.max_modificadores, `modifier_groups[${index}].assignment.maximomodificadores`, { min: 0 }),
        forzarcaptura: asBool(assignmentInput.forzarcaptura ?? assignmentInput.forzar_captura),
        captura_incluido: asBool(assignmentInput.captura_incluido),
      },
      modifiers,
    };
  });
  return {
    product,
    detail,
    modifier_groups: groups,
    dry_run: payload.dry_run !== false,
  };
}

async function inspect(pool) {
  return inspectTables(pool, {
    productos: ['idproducto', 'descripcion', 'idgrupo', 'nombrecorto'],
    productosdetalle: ['idproducto', 'idempresa', 'precio', 'bloqueado', 'secuenciacompuesto', 'finalizarsecuenciacompuesto', 'heredarmonitormodificadores', 'usarmultiplicadorprodcomp', 'permitirprodcompenmodif', 'ocultarmitades'],
    gruposmodificadores: ['idgruposmodificadores', 'descripcion'],
    gruposmodificadoresproductos: ['idproducto', 'idgruposmodificadores', 'idempresa', 'incluidos', 'prioridad', 'forzarcaptura', 'maximomodificadores', 'captura_incluido'],
    modificadores: ['idproducto', 'idmodificador', 'precio', 'idgruposmodificadores', 'idempresa'],
  });
}

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

function requiredSchemaErrors(metadata) {
  return [
    ...requireColumns(metadata, 'productos', ['idproducto', 'descripcion']),
    ...requireColumns(metadata, 'productosdetalle', ['idproducto', 'idempresa', 'precio']),
    ...requireColumns(metadata, 'gruposmodificadores', ['idgruposmodificadores']),
    ...requireColumns(metadata, 'gruposmodificadoresproductos', ['idproducto', 'idgruposmodificadores']),
    ...requireColumns(metadata, 'modificadores', ['idproducto', 'idgruposmodificadores']),
  ];
}

async function scalar(pool, sqlText, inputs = {}) {
  const request = pool.request();
  for (const [key, value] of Object.entries(inputs)) request.input(key, sql.NVarChar(80), value);
  const result = await request.query(sqlText);
  const row = result.recordset?.[0] || {};
  return Number(row[Object.keys(row)[0]] || 0);
}

async function dryRun({ payload, pool }) {
  const metadata = await inspect(pool);
  const errors = requiredSchemaErrors(metadata);
  if (errors.length) schemaMismatch(errors);
  const productExists = await scalar(pool, `SELECT COUNT(*) AS count FROM ${qname('productos')} WHERE idproducto=@idproducto`, { idproducto: payload.product.idproducto });
  const detailExists = payload.product.idempresa
    ? await scalar(pool, `SELECT COUNT(*) AS count FROM ${qname('productosdetalle')} WHERE idproducto=@idproducto AND idempresa=@idempresa`, { idproducto: payload.product.idproducto, idempresa: payload.product.idempresa })
    : 0;
  const groupPlans = [];
  for (const group of payload.modifier_groups) {
    const groupExists = await scalar(pool, `SELECT COUNT(*) AS count FROM ${qname('gruposmodificadores')} WHERE idgruposmodificadores=@idgrupo`, { idgrupo: group.idgruposmodificadores });
    const assignmentWhereEmpresa = group.assignment.idempresa && optionalColumn(metadata, 'gruposmodificadoresproductos', 'idempresa') ? ' AND idempresa=@idempresa' : '';
    const assignmentExists = await scalar(pool, `SELECT COUNT(*) AS count FROM ${qname('gruposmodificadoresproductos')} WHERE idproducto=@idproducto AND idgruposmodificadores=@idgrupo${assignmentWhereEmpresa}`, {
      idproducto: payload.product.idproducto,
      idgrupo: group.idgruposmodificadores,
      idempresa: group.assignment.idempresa,
    });
    const modifiers = [];
    for (const modifier of group.modifiers) {
      const modifierProductExists = await scalar(pool, `SELECT COUNT(*) AS count FROM ${qname('productos')} WHERE idproducto=@idproducto`, { idproducto: modifier.idproducto });
      const modifierWhereEmpresa = modifier.idempresa && optionalColumn(metadata, 'modificadores', 'idempresa') ? ' AND idempresa=@idempresa' : '';
      const modifierColumn = optionalColumn(metadata, 'modificadores', 'idmodificador') ? 'idmodificador' : 'idproducto';
      const parentClause = optionalColumn(metadata, 'modificadores', 'idmodificador') ? ' AND idproducto=@parent_idproducto' : '';
      const modifierExists = await scalar(pool, `SELECT COUNT(*) AS count FROM ${qname('modificadores')} WHERE idgruposmodificadores=@idgrupo AND ${modifierColumn}=@idproducto${parentClause}${modifierWhereEmpresa}`, {
        idgrupo: group.idgruposmodificadores,
        idproducto: modifier.idproducto,
        parent_idproducto: payload.product.idproducto,
        idempresa: modifier.idempresa,
      });
      modifiers.push({
        idproducto: modifier.idproducto,
        action: modifierExists ? 'skip_existing_relation' : 'upsert_relation',
        product_action: modifierProductExists ? 'use_existing_product' : 'insert_product',
        precio: modifier.precio,
      });
    }
    groupPlans.push({
      idgruposmodificadores: group.idgruposmodificadores,
      action: groupExists ? 'update' : 'insert',
      assignment_action: assignmentExists ? 'skip_existing_relation' : 'upsert_relation',
      modifiers,
    });
  }
  return {
    ok: true,
    dry_run: true,
    entity_type: 'product_with_modifiers',
    entity_id: payload.product.idproducto,
    actions: ['productos', 'productosdetalle', 'gruposmodificadores', 'gruposmodificadoresproductos', 'modificadores'],
    planned_product: {
      idproducto: payload.product.idproducto,
      action: productExists ? 'update' : 'insert',
      detail_action: detailExists ? 'update' : 'insert',
      idempresa: payload.product.idempresa,
    },
    modifier_groups: groupPlans,
    optional_columns: {
      productosdetalle: {
        secuenciacompuesto: optionalColumn(metadata, 'productosdetalle', 'secuenciacompuesto'),
        finalizarsecuenciacompuesto: optionalColumn(metadata, 'productosdetalle', 'finalizarsecuenciacompuesto'),
        heredarmonitormodificadores: optionalColumn(metadata, 'productosdetalle', 'heredarmonitormodificadores'),
        usarmultiplicadorprodcomp: optionalColumn(metadata, 'productosdetalle', 'usarmultiplicadorprodcomp'),
        permitirprodcompenmodif: optionalColumn(metadata, 'productosdetalle', 'permitirprodcompenmodif'),
        ocultarmitades: optionalColumn(metadata, 'productosdetalle', 'ocultarmitades'),
      },
    },
  };
}

async function execute({ config }) {
  if (config.enableProductModifierWrites !== true) {
    throw commandError('write_not_enabled', 'UPSERT_PRODUCT_WITH_MODIFIERS real permanece deshabilitado hasta validar snapshot SoftRestaurant');
  }
  throw commandError('write_not_enabled', 'UPSERT_PRODUCT_WITH_MODIFIERS execute preparado detras de bandera; implementacion real pendiente de aprobacion');
}

const mapper = createMapper({ commandType: 'UPSERT_PRODUCT_WITH_MODIFIERS', validate, dryRun, execute });

module.exports = { upsertProductWithModifiers: mapper.run, mapper, validate, dryRun, execute, inspect };
