'use strict';

const crypto = require('crypto');
const logger = require('../utils/logger');
const { getPool } = require('../db');
const { buildBatch } = require('./syncPayloadBuilder');
const { sendBatch, sendSyncStatus } = require('./syncClient');
const { getWatermark, setWatermark, ensureWatermarkScope } = require('./syncWatermark');
const { updateState } = require('./agentState');
const syncQueue = require('./syncQueue');
const ventas = require('./mssqlExtractors/ventasExtractor');
const saleItems = require('./mssqlExtractors/saleItemsExtractor');
const productos = require('./mssqlExtractors/productosExtractor');
const cortes = require('./mssqlExtractors/cortesExtractor');
const inventario = require('./mssqlExtractors/inventarioExtractor');
const inventoryItems = require('./mssqlExtractors/inventoryItemsExtractor');
const almacenes = require('./mssqlExtractors/almacenesExtractor');
const openTableSessions = require('./mssqlExtractors/openTableSessionsExtractor');
const productPrices = require('./mssqlExtractors/productPricesExtractor');
const productModifierGroups = require('./mssqlExtractors/productModifierGroupsExtractor');
const productModifierAssignments = require('./mssqlExtractors/productModifierAssignmentsExtractor');
const productModifiers = require('./mssqlExtractors/productModifiersExtractor');
const formasPago = require('./mssqlExtractors/formasPagoExtractor');
const salePayments = require('./mssqlExtractors/salePaymentsExtractor');
const cancelaciones = require('./mssqlExtractors/cancelacionesExtractor');
const cashMovements = require('./mssqlExtractors/cashMovementsExtractor');

const extractors = [
  ventas,
  openTableSessions,
  saleItems,
  salePayments,
  productos,
  productPrices,
  productModifierGroups,
  productModifierAssignments,
  productModifiers,
  cortes,
  almacenes,
  inventoryItems,
  inventario,
  formasPago,
  cancelaciones,
  cashMovements,
];

function yieldToLocalApi(delayMs = Number(process.env.ARKHON_SYNC_EXTRACTOR_PAUSE_MS || 25)) {
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, delayMs)));
}

function maxWatermark(rows) {
  return rows.reduce((max, row) => {
    const value = row.source_updated_at || row.source_created_at;
    if (!value) return max;
    const iso = new Date(value).toISOString();
    return !max || iso > max ? iso : max;
  }, null);
}

async function flushQueue(config) {
  const pending = await syncQueue.due(config.syncBatchSize || 50);
  if (!pending.length) return;
  for (const entry of pending) {
    try {
      await syncQueue.markInFlight(entry.batch.batch_id);
      await sendBatch(config, entry.batch);
      await syncQueue.markSynced(entry.batch.batch_id);
    } catch (err) {
      await syncQueue.markFailed(entry.batch.batch_id, err.message, { maxRetries: config.syncMaxRetries || 10 });
    }
  }
}

async function runOnce(config) {
  const cycleStartedAt = new Date().toISOString();
  updateState((state) => {
    state.syncRuntime = { status: 'running', startedAt: cycleStartedAt, completedAt: null, error: null };
    return state;
  });
  try {
    const scope = ensureWatermarkScope(config);
    if (scope.changed) logger.info({ event: 'agent_sync_scope_changed', previous_scope: scope.previousScope, scope: scope.scope });
    await flushQueue(config);
    const pool = await getPool();
    for (const extractor of extractors) {
      await yieldToLocalApi();
      const watermarkFrom = getWatermark(extractor.entityType);
      const rows = await extractor.extract(pool, watermarkFrom, config.syncBatchSize);
      if (!rows.length) continue;

      const watermarkTo = maxWatermark(rows) || new Date().toISOString();
      const batch = buildBatch(config, extractor.entityType, watermarkFrom, watermarkTo, rows);
      try {
        await sendSyncStatus(config, { entity_type: extractor.entityType, status: 'started', batch_id: batch.batch_id });
        await sendBatch(config, batch);
        setWatermark(extractor.entityType, watermarkTo);
        await sendSyncStatus(config, { entity_type: extractor.entityType, status: 'completed', batch_id: batch.batch_id, items: rows.length });
      } catch (err) {
        logger.warn({ event: 'agent_sync_batch_failed', entity_type: extractor.entityType, error: err.message });
        if (config.offlineQueue) await syncQueue.enqueue(batch);
        await sendSyncStatus(config, { entity_type: extractor.entityType, status: 'failed', batch_id: batch.batch_id, error: err.message }).catch(() => {});
      }
    }
    const completedAt = new Date().toISOString();
    updateState((state) => {
      state.lastSyncAt = completedAt;
      state.syncRuntime = { status: 'idle', startedAt: cycleStartedAt, completedAt, error: null };
      return state;
    });
  } catch (err) {
    updateState((state) => {
      state.syncRuntime = { status: 'failed', startedAt: cycleStartedAt, completedAt: new Date().toISOString(), error: String(err.message || err).slice(0, 300) };
      return state;
    });
    throw err;
  }
}

async function previewEntity(entityType, limit = 50) {
  const extractor = extractors.find((candidate) => candidate.entityType === entityType);
  if (!extractor) {
    const error = new Error(`Entidad no soportada para previsualización: ${entityType}`);
    error.status = 404;
    throw error;
  }
  const safeLimit = Math.min(Math.max(Number(limit) || 50, 1), 500);
  const watermark = getWatermark(extractor.entityType);
  const pool = await getPool();
  const rows = await extractor.extract(pool, watermark, safeLimit);
  const identities = rows.map((row) => String(row.external_id || row.id || row.folio || '')).filter(Boolean);
  const digest = crypto.createHash('sha256').update(JSON.stringify(rows)).digest('hex');
  return {
    entity_type: extractor.entityType,
    watermark,
    detected: rows.length,
    next_watermark: maxWatermark(rows) || watermark,
    sample_external_ids: identities.slice(0, 20),
    payload_sha256: digest,
    observational: true,
    cursor_advanced: false,
  };
}

function startSyncScheduler(config) {
  let running = false;
  async function tick() {
    if (running) return;
    running = true;
    try {
      await runOnce(config);
    } catch (err) {
      logger.warn({ event: 'agent_sync_tick_failed', error: err.message });
    } finally {
      running = false;
    }
  }

  tick();
  const intervalSeconds = Math.max(5, Number(config.syncIntervalSeconds) || 10);
  const timer = setInterval(tick, intervalSeconds * 1000);
  timer.unref?.();
  return timer;
}

module.exports = { startSyncScheduler, runOnce, flushQueue, previewEntity, extractors, yieldToLocalApi };
