'use strict';

const logger = require('../../utils/logger');
const { getPool } = require('../../db');
const bridge = require('../../agent/softrestaurant-bridge');
const outbox = require('./local-outbox');
const inbox = require('./local-inbox');
const cloud = require('./cloud-client');
const commandHandlers = require('./command-handlers');
const storage = require('./storage');
const administrativeEvents = require('./administrative-events');

function enabled() {
  return String(process.env.ARKHON_MULTIBRANCH_SYNC_ENABLED || 'false').toLowerCase() === 'true';
}

function branchMap() {
  try {
    const value = JSON.parse(process.env.ARKHON_MULTIBRANCH_BRANCH_MAP_JSON || '{}');
    return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
  } catch (_) {
    return {};
  }
}

function updateCursors(events) {
  storage.update('cursors', {}, (cursors) => {
    for (const event of events) {
      const current = cursors[event.source_table];
      if (!current || String(event.source_updated_at) >= String(current.watermark || '')) {
        cursors[event.source_table] = {
          watermark: event.payload_json?.metadata?.cursor?.watermark || event.source_updated_at,
          key: event.payload_json?.metadata?.cursor?.key || event.source_id,
        };
      }
    }
    return cursors;
  });
}

function createCoordinator(deps = {}) {
  const localBridge = deps.bridge || bridge;
  const localOutbox = deps.outbox || outbox;
  const localInbox = deps.inbox || inbox;
  const cloudClient = deps.cloud || cloud;
  const handlers = deps.commandHandlers || commandHandlers;
  const poolProvider = deps.getPool || getPool;

  async function extract(config) {
    const pool = await poolProvider();
    const events = await localBridge.extractCanonicalEvents(pool, {
      tenantId: config.tenantId,
      branchId: config.branchId,
      agentId: config.agentId,
      metadata: {
        matrix_branch_id: process.env.ARKHON_MATRIX_BRANCH_ID || undefined,
        branch_map: branchMap(),
      },
      recipesGlobal: String(process.env.ARKHON_MULTIBRANCH_RECIPES_GLOBAL || 'false').toLowerCase() === 'true',
    }, storage.read('cursors', {}), config.syncBatchSize || 100);
    localOutbox.enqueue(events);
    updateCursors(events);
    return events.length;
  }

  async function push(config) {
    const events = localOutbox.lease(config.syncBatchSize || 100);
    if (!events.length) return 0;
    try {
      const response = await cloudClient.push(config, events);
      const acceptedKeys = new Set((response.results || []).map((result) => result.idempotencyKey));
      localOutbox.markCloudReceived(events.filter((event) => acceptedKeys.has(event.idempotency_key)).map((event) => event.event_id));
      return acceptedKeys.size;
    } catch (err) {
      for (const event of events) localOutbox.markFailed(event.event_id, err.message, { maxRetries: config.syncMaxRetries, baseDelayMs: config.syncBackoffSeconds * 1000 });
      throw err;
    }
  }

  async function pull(config) {
    const response = await cloudClient.pull(config, { limit: config.syncBatchSize || 100 });
    const events = response.events || [];
    localInbox.enqueue(events);
    return events.length;
  }

  async function applyAndAck(config) {
    const events = localInbox.lease(config.syncBatchSize || 100);
    for (const event of events) {
      try {
        localInbox.markApplied(event.event_id, await handlers.apply(event));
      } catch (err) {
        localInbox.markFailed(event.event_id, err.message, {
          maxRetries: config.syncMaxRetries,
          baseDelayMs: config.syncBackoffSeconds * 1000,
          result: ['SOFTRESTAURANT_WRITE_DISABLED', 'SOFTRESTAURANT_WRITER_NOT_IMPLEMENTED'].includes(err.code) ? 'write_disabled' : 'failed_validation',
        });
      }
    }
    let acknowledged = 0;
    for (let pass = 0; pass < 5; pass += 1) {
      const acknowledgements = localInbox.pendingAcknowledgements(config.syncBatchSize || 100);
      if (!acknowledgements.length) break;
      const response = await cloudClient.ack(config, acknowledgements);
      const accepted = new Set(response.event_ids || []);
      const sent = acknowledgements.filter((acknowledgement) => accepted.has(acknowledgement.event_id));
      localInbox.markAcknowledged(sent);
      acknowledged += sent.length;
      if (!sent.length) break;
    }
    return acknowledged;
  }

  async function runOnce(config) {
    const result = { extracted: 0, pushed: 0, pulled: 0, acknowledged: 0 };
    try { result.pushed = await push(config); } catch (err) { logger.warn({ event: 'multibranch_push_failed', error: err.message }); }
    try { result.pulled = await pull(config); } catch (err) { logger.warn({ event: 'multibranch_pull_failed', error: err.message }); }
    try { result.acknowledged = await applyAndAck(config); } catch (err) { logger.warn({ event: 'multibranch_ack_failed', error: err.message }); }
    try { result.extracted = await extract(config); } catch (err) { logger.warn({ event: 'multibranch_extract_failed', error: err.message }); }
    return result;
  }

  function start(config) {
    if (!enabled()) return { enabled: false };
    let running = false;
    const tick = async () => {
      if (running) return;
      running = true;
      try {
        await runOnce(config);
      } catch (err) {
        logger.warn({ event: 'multibranch_sync_failed', error: err.message });
      } finally {
        running = false;
      }
    };
    tick();
    const timer = setInterval(tick, Math.max(config.syncIntervalSeconds || 30, 5) * 1000);
    timer.unref?.();
    return { enabled: true, timer };
  }

  return { extract, push, pull, applyAndAck, runOnce, start };
}

module.exports = { enabled, createCoordinator, start: (config) => createCoordinator().start(config), outbox, inbox, cloud, commandHandlers, administrativeEvents };
