'use strict';

const crypto = require('crypto');
const logger = require('../utils/logger');
const sync = require('../modules/sync');
const { executeCommand } = require('./commandExecutor');
const { createRealtimeToken } = require('./syncClient');
const { signRequest, PROTOCOL_VERSION } = require('./agentAuth');

let ioClient = null;
const realtimeState = {
  connected: false,
  session_expires_at: null,
  last_error: null,
};

function getIoClient() {
  if (ioClient !== null) return ioClient;
  try {
    ioClient = require('socket.io-client');
  } catch (_) {
    ioClient = false;
  }
  return ioClient;
}

function startCloudRealtime(config) {
  const io = getIoClient();
  if (!io || !config.cloudUrl || !config.agentId || !config.agentKey) {
    if (!io) logger.warn({ event: 'cloud_realtime_unavailable', reason: 'socket.io-client missing' });
    return { enabled: false };
  }

  const socket = io(`${config.cloudUrl}/cloud-connectors`, {
    transports: ['websocket', 'polling'],
    reconnection: true,
    reconnectionAttempts: Infinity,
    reconnectionDelay: 1000,
    reconnectionDelayMax: 30000,
    auth: async (callback) => {
      try {
        callback(await buildRealtimeAuth(config));
      } catch (err) {
        logger.warn({ event: 'cloud_realtime_token_failed', error: err.message });
        callback({ connector_id: config.agentId });
      }
    },
  });

  socket.on('connect', () => {
    logger.info({ event: 'cloud_realtime_connected', connector_id: config.agentId });
    socket.emit('connector:hello', {
      connector_id: config.agentId,
      version: config.agentVersion,
      capabilities: ['cloud_commands', 'offline_queue', 'billing_qr_printing'],
    });
    flushPendingEvents(socket).catch(() => {});
  });

  socket.on('connector:authenticated', (payload) => {
    realtimeState.connected = true;
    realtimeState.session_expires_at = payload.session_expires_at || null;
    realtimeState.last_error = null;
    logger.info({ event: 'cloud_realtime_authenticated', connector_id: payload.connector_id, branch_id: payload.branch_id });
  });

  socket.on('cloud:command', async (command) => {
    await handleCommand(socket, command, config);
  });

  socket.on('cloud:event_ack', (payload = {}) => {
    if (payload.event_id) sync.queue.markEventSynced(payload.event_id, payload).catch((err) => logger.warn({ event: 'cloud_event_ack_persist_failed', error: err.message }));
  });

  socket.on('sync:request_push', () => {
    sync.service.flushOnce().catch((err) => logger.warn({ event: 'cloud_realtime_force_push_failed', error: err.message }));
  });

  socket.on('sync:request_pull', () => {
    socket.emit('connector:event', { event_type: 'sync.pull.requested', event_id: commandId(), at: new Date().toISOString() });
  });

  socket.on('disconnect', (reason) => {
    realtimeState.connected = false;
    realtimeState.last_error = reason;
    logger.warn({ event: 'cloud_realtime_disconnected', reason });
  });

  const heartbeatMs = Math.max(10, config.heartbeatIntervalSeconds || 30) * 1000;
  const timer = setInterval(async () => {
    if (!socket.connected) return;
    try {
      const status = await sync.service.getStatus();
      socket.emit('connector:heartbeat', {
        connector_id: config.agentId,
        last_sync_at: status.queue.last_sync_at,
        pending_events: status.queue.pending || 0,
        uptime: process.uptime(),
        at: new Date().toISOString(),
      });
    } catch (err) {
      logger.warn({ event: 'cloud_realtime_heartbeat_status_failed', error: err.message });
    }
  }, heartbeatMs);
  timer.unref?.();

  return { enabled: true, socket, timer };
}

async function buildRealtimeAuth(config) {
  const issued = await createRealtimeToken(config);
  const token = issued.realtime_token;
  if (!token) throw new Error('realtime_token_missing');
  const timestamp = Date.now().toString();
  const nonce = crypto.randomBytes(24).toString('base64url');
  return {
    connector_id: config.agentId,
    key_id: config.agentKeyId || config.agent_key_id || config.connectorKeyId || config.connector_key_id,
    realtime_token: token,
    timestamp,
    nonce,
    signature: signRequest({ method: 'GET', path: '/cloud-connectors', timestamp, nonce, body: '', secret: token }),
    protocol_version: issued.protocol_version || PROTOCOL_VERSION,
  };
}

async function handleCommand(socket, command, config, deps = {}) {
  if (!command?.command_id || !command?.command_type) return;
  try {
    const result = await executeCommand(command, config, deps);
    socket.emit('connector:command_ack', {
      command_id: command.command_id,
      idempotency_key: command.idempotency_key,
      duplicate: Boolean(result.duplicate),
      result: result.result || null,
      applied_at: new Date().toISOString(),
    });
  } catch (err) {
    await sync.queue.markCommandError(command, err.message);
    socket.emit('connector:command_error', {
      command_id: command.command_id,
      code: String(err.code || 'command_error'),
      error: err.message,
      outcome: err.outcome || null,
      at: new Date().toISOString(),
    });
  }
}

async function flushPendingEvents(socket) {
  const events = (await sync.queue.listEvents({ status: 'pending' })).slice(0, 100);
  for (const event of events) {
    const batchId = commandId();
    const lease = await sync.queue.markSyncing([event.event_id], batchId);
    socket.emit('connector:event', {
      event_id: event.event_id,
      event_type: event.entity_type,
      entity_id: event.entity_id,
      operation: event.operation,
      payload: event.payload_json,
      idempotency_key: event.idempotency_key,
      batch_id: batchId,
      lease_id: lease?.lease_id,
    });
  }
}

function commandId() {
  return crypto.randomUUID();
}

function getRealtimeState() {
  return { ...realtimeState };
}

module.exports = { startCloudRealtime, handleCommand, buildRealtimeAuth, getRealtimeState };
