'use strict';

// Instala servicios reales de Windows para ARKHON.
// Ejecutar como Administrador: node scripts/install-service.js [--json] [--force-repair|--force-reinstall]

const fs = require('fs');
const os = require('os');
const path = require('path');
const { execFileSync } = require('child_process');

const args = new Set(process.argv.slice(2));
const jsonMode = args.has('--json');
const forceRepair = args.has('--force-repair') || args.has('--force-reinstall');
const forceReinstall = args.has('--force-reinstall');
const skipUpdater = args.has('--no-updater');

const ROOT = path.resolve(__dirname, '..');
const PROGRAMDATA = process.env.ProgramData || 'C:\\ProgramData';
const ARKHON_DATA_DIR = path.join(PROGRAMDATA, 'ARKHON');
const AGENT_DATA_DIR = process.env.ARKHON_AGENT_DATA_DIR || path.join(ARKHON_DATA_DIR, 'Agent');
const UPDATER_DATA_DIR = process.env.ARKHON_UPDATER_DATA_DIR || path.join(ARKHON_DATA_DIR, 'Updater');
const CONFIG_DIR = process.env.CONFIG_PATH || path.join(AGENT_DATA_DIR, 'config');
const LOG_DIR = process.env.ARKHON_AGENT_LOG_DIR || path.join(AGENT_DATA_DIR, 'logs');
const UPDATER_LOG_DIR = process.env.ARKHON_UPDATER_LOG_DIR || path.join(UPDATER_DATA_DIR, 'logs');
const QUEUE_PATH = process.env.ARKHON_AGENT_QUEUE_PATH || path.join(AGENT_DATA_DIR, 'queue.sqlite');
const DIAGNOSTICS_DIR = path.join(AGENT_DATA_DIR, 'diagnostics');
const SERVICE_DIR = path.join(ROOT, 'service');
const CANONICAL_RUNTIME_PATH = path.join(process.env.ProgramFiles || 'C:\\Program Files', 'ARKHON Monitor', 'ARKHON Monitor.exe');
const DIAGNOSTIC_STAMP = new Date().toISOString().replace(/[-:]/g, '').replace(/\..+$/, '').replace('T', '-');
const DIAGNOSTIC_PATH = path.join(DIAGNOSTICS_DIR, `service-install-${DIAGNOSTIC_STAMP}.json`);
const ENVIRONMENT_PATH = path.join(DIAGNOSTICS_DIR, `environment-${DIAGNOSTIC_STAMP}.json`);
const LOG_PATH = path.join(DIAGNOSTICS_DIR, `service-install-${DIAGNOSTIC_STAMP}.log`);
const LOW_MEMORY = os.totalmem() / (1024 ** 3) <= 4.5;
const EXPECTED_VERSION = process.env.ARKHON_EXPECTED_VERSION || '';

const LEGACY_SERVICE_NAMES = [
  'ARKHON Agent',
  'ARKHON Updater',
  'ArkhonMonitorConnector',
  'ARKHON Monitor',
  'arkhonagent',
  'arkhonagent.exe',
  'arkhonupdater',
  'arkhonupdater.exe',
];

const result = {
  ok: false,
  action: 'install',
  serviceInstalled: false,
  serviceRunning: false,
  services: [],
  events: [],
  errors: [],
  warnings: [],
  commands: [],
  previousServices: [],
  removedServices: [],
  createdServices: [],
  diagnosticPath: DIAGNOSTIC_PATH,
};

function print(message) {
  if (!jsonMode) console.log(message);
}

function log(message) {
  try {
    fs.mkdirSync(DIAGNOSTICS_DIR, { recursive: true });
    fs.appendFileSync(LOG_PATH, `[${new Date().toISOString()}] ${message}\n`, 'utf8');
  } catch (_) {}
}

function recordEvent(event, details = {}) {
  const entry = {
    event,
    at: new Date().toISOString(),
    ...details,
  };
  result.events.push(entry);
  log(`EVENT ${event} ${JSON.stringify(details)}`);
  return entry;
}

function addWarning(message) {
  result.warnings.push(message);
  log(`WARN ${message}`);
  print(`[WARN] ${message}`);
}

function addError(message) {
  result.errors.push(message);
  log(`ERROR ${message}`);
  print(`[ERROR] ${message}`);
}

function finish(code) {
  result.ok = result.errors.length === 0;
  try {
    fs.mkdirSync(DIAGNOSTICS_DIR, { recursive: true });
    fs.writeFileSync(DIAGNOSTIC_PATH, JSON.stringify(result, null, 2), 'utf8');
    fs.writeFileSync(ENVIRONMENT_PATH, JSON.stringify({
      createdAt: new Date().toISOString(),
      hostname: os.hostname(),
      user: os.userInfo().username,
      admin: code === 0 || !result.errors.some((error) => String(error).includes('admin_required')),
      windowsVersion: os.release(),
      arch: os.arch(),
      processArch: process.arch,
      programData: PROGRAMDATA,
      installDir: ROOT,
      serviceDir: SERVICE_DIR,
      execPath: process.execPath,
      runtime: result.runtime || null,
      candidateRuntime: process.execPath,
      canonicalRuntime: CANONICAL_RUNTIME_PATH,
      npmAvailable: Boolean(process.env.npm_execpath),
      PATH: process.env.PATH || '',
      TEMP: process.env.TEMP || '',
    }, null, 2), 'utf8');
  } catch (error) {
    result.warnings.push(`No se pudo escribir diagnostico: ${error.message}`);
  }

  if (jsonMode) process.stdout.write(`${JSON.stringify(result)}\n`);
  process.exit(code);
}

function requireAdmin() {
  try {
    execFileSync('net.exe', ['session'], { stdio: 'pipe', timeout: 8000 });
  } catch (_) {
    throw new Error('admin_required: ejecuta el instalador como Administrador.');
  }
}

function run(command, argsList, options = {}) {
  const entry = { command, args: argsList, cwd: options.cwd || ROOT };
  try {
    const stdout = execFileSync(command, argsList, {
      cwd: options.cwd || ROOT,
      stdio: 'pipe',
      timeout: options.timeout || 20000,
      windowsHide: true,
    }).toString();
    entry.ok = true;
    entry.stdout = stdout;
    result.commands.push(entry);
    log(`RUN ${command} ${argsList.join(' ')}\n${stdout}`);
    return stdout;
  } catch (error) {
    entry.ok = false;
    entry.stdout = error.stdout ? error.stdout.toString() : '';
    entry.stderr = error.stderr ? error.stderr.toString() : '';
    entry.error = error.message;
    result.commands.push(entry);
    log(`RUN FAILED ${command} ${argsList.join(' ')}\n${entry.stdout}\n${entry.stderr}\n${entry.error}`);
    throw error;
  }
}

function tryRun(command, argsList, options = {}) {
  try {
    return { ok: true, stdout: run(command, argsList, options) };
  } catch (error) {
    return { ok: false, error, stdout: error.stdout ? error.stdout.toString() : '', stderr: error.stderr ? error.stderr.toString() : '' };
  }
}

function serviceExists(name) {
  return tryRun('sc.exe', ['query', name], { timeout: 8000 }).ok;
}

function stopService(name) {
  if (!serviceExists(name)) return;
  tryRun('sc.exe', ['stop', name], { timeout: 15000 });
}

function deleteService(name) {
  const before = getServiceCim(name);
  if (!before && !serviceExists(name)) return false;
  stopService(name);
  const deleted = tryRun('sc.exe', ['delete', name], { timeout: 15000 });
  if (!deleted.ok) throw new Error(`No se pudo eliminar servicio ${name}: ${deleted.stderr || deleted.stdout || deleted.error.message}`);
  result.removedServices.push({ name, before });
  return true;
}

function sleep(ms) {
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
}

function getServiceCim(name) {
  const ps = [
    '$ErrorActionPreference = "Stop"',
    `$svc = Get-CimInstance Win32_Service -Filter "Name='${name.replace(/'/g, "''")}'"`,
    'if ($null -eq $svc) { exit 3 }',
    '$svc | Select-Object Name,DisplayName,State,StartMode,StartName,PathName | ConvertTo-Json -Compress',
  ].join('; ');
  const probe = tryRun('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', ps], { timeout: 10000 });
  if (!probe.ok || !probe.stdout.trim()) return null;
  return JSON.parse(probe.stdout);
}

function getScQuery(name) {
  const query = tryRun('sc.exe', ['query', name], { timeout: 8000 });
  return query.ok ? query.stdout : '';
}

function getScFailure(name) {
  const query = tryRun('sc.exe', ['qfailure', name], { timeout: 8000 });
  return query.ok ? query.stdout : '';
}

function waitForServiceDeleted(name) {
  const started = Date.now();
  while (Date.now() - started < 30000) {
    if (!serviceExists(name)) return true;
    sleep(500);
  }
  return !serviceExists(name);
}

function waitForServiceState(name, expectedState) {
  const started = Date.now();
  while (Date.now() - started < 60000) {
    const cim = getServiceCim(name);
    if (cim && String(cim.State).toLowerCase() === expectedState.toLowerCase()) return cim;
    sleep(1000);
  }
  return getServiceCim(name);
}

function xmlEscape(value) {
  return String(value)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&apos;');
}

function xmlUnescape(value) {
  return String(value)
    .replace(/&apos;/g, "'")
    .replace(/&quot;/g, '"')
    .replace(/&gt;/g, '>')
    .replace(/&lt;/g, '<')
    .replace(/&amp;/g, '&');
}

function isExistingFile(candidate) {
  try {
    return Boolean(candidate && fs.existsSync(candidate) && fs.statSync(candidate).isFile());
  } catch (_) {
    return false;
  }
}

function isLegacyAppDataRuntime(candidate) {
  return /\\AppData\\Local\\Programs\\ARKHON Monitor\\ARKHON Monitor\.exe$/i.test(String(candidate || ''));
}

function runtimeCandidateError(candidate) {
  if (!candidate) return 'empty';
  if (!isExistingFile(candidate)) return 'missing';
  if (!/\.exe$/i.test(candidate)) return 'not_exe';
  if (isLegacyAppDataRuntime(candidate)) return 'legacy_appdata_runtime';
  return null;
}

function resolveRuntimeExecutable() {
  const explicitRuntime = process.env.ARKHON_RUNTIME_EXECUTABLE || '';
  const candidates = [];
  if (explicitRuntime) candidates.push({ source: 'ARKHON_RUNTIME_EXECUTABLE', path: explicitRuntime });
  candidates.push({ source: 'canonical_program_files', path: CANONICAL_RUNTIME_PATH });
  candidates.push({ source: 'process_execPath', path: process.execPath });

  const inspected = candidates.map((candidate) => ({
    ...candidate,
    exists: isExistingFile(candidate.path),
    legacyAppData: isLegacyAppDataRuntime(candidate.path),
    error: runtimeCandidateError(candidate.path),
  }));

  const selected = inspected.find((candidate) => !candidate.error && !candidate.legacyAppData);

  recordEvent('runtime_resolution', { selected: selected ? selected.path : null, candidates: inspected });

  if (!selected) {
    throw new Error(`runtime_executable_not_found: no existe runtime valido. Revisados: ${inspected.map((item) => `${item.source}=${item.path || '<empty>'}:${item.error || 'ok'}`).join('; ')}`);
  }

  result.runtime = selected.path;
  return selected.path;
}

function readXmlExecutable(xmlPath) {
  if (!fs.existsSync(xmlPath)) return null;
  const xml = fs.readFileSync(xmlPath, 'utf8');
  const match = xml.match(/<executable>([\s\S]*?)<\/executable>/i);
  return match ? xmlUnescape(match[1].trim()) : null;
}

function readXmlArguments(xmlPath) {
  if (!fs.existsSync(xmlPath)) return null;
  const xml = fs.readFileSync(xmlPath, 'utf8');
  const match = xml.match(/<arguments>([\s\S]*?)<\/arguments>/i);
  return match ? xmlUnescape(match[1].trim()) : null;
}

function validateXmlArguments(definition, errors) {
  const xmlArguments = readXmlArguments(definition.xmlPath);
  if (!xmlArguments) {
    errors.push('winsw_xml_arguments_missing');
    return;
  }
  if (!xmlArguments.includes(definition.script)) {
    errors.push(`winsw_xml_arguments_mismatch:${xmlArguments}`);
  }
  if (!path.normalize(definition.script).toLowerCase().startsWith(path.normalize(PROGRAMDATA_ROOT).toLowerCase())) {
    errors.push(`winsw_script_not_programdata:${definition.script}`);
  }
  if (/\\AppData\\Local\\Programs\\ARKHON Monitor(\\|$)/i.test(xmlArguments)) {
    errors.push(`legacy_appdata_runtime:${xmlArguments}`);
  }
}

function validateXmlExecutable(definition, phase) {
  const executable = readXmlExecutable(definition.xmlPath);
  const errors = [];
  if (!fs.existsSync(definition.xmlPath)) errors.push(`winsw_xml_missing:${definition.xmlPath}`);
  if (!executable) errors.push('winsw_xml_executable_missing');
  if (executable && !isExistingFile(executable)) errors.push(`runtime_executable_not_found:${executable}`);
  if (executable && isLegacyAppDataRuntime(executable)) errors.push(`legacy_appdata_runtime:${executable}`);
  if (executable && path.normalize(executable).toLowerCase() !== path.normalize(definition.runtime).toLowerCase()) {
    errors.push(`winsw_xml_executable_mismatch:${executable}`);
  }
  validateXmlArguments(definition, errors);
  if (errors.length > 0) {
    recordEvent('service_validation_failed', { phase, service: definition.name, errors });
    throw new Error(`service_validation_failed: ${definition.name}: ${errors.join(', ')}`);
  }
  recordEvent('winsw_xml_executable_validated', { phase, service: definition.name, executable });
  return executable;
}

function findUpdaterScript() {
  const candidates = [
    path.join(ROOT, '..', 'arkhon-updater', 'index.js'),
    path.join(ROOT, '..', 'arkhon-updater', 'server.js'),
    path.join(ROOT, 'arkhon-updater', 'index.js'),
    path.join(ROOT, 'resources', 'arkhon-updater', 'index.js'),
    path.join(ROOT, 'resources', 'arkhon-updater', 'server.js'),
    path.join(process.resourcesPath || '', 'arkhon-updater', 'index.js'),
    path.join(process.resourcesPath || '', 'arkhon-updater', 'server.js'),
  ].filter(Boolean);
  return candidates.find((candidate) => fs.existsSync(candidate)) || null;
}

function findWinSwBinary() {
  const candidates = [
    path.join(ROOT, 'service-runtime', 'winsw.exe'),
    path.join(ROOT, 'node_modules', 'node-windows', 'bin', 'winsw', 'winsw.exe'),
    path.join(process.resourcesPath || '', 'arkhon-app', 'service-runtime', 'winsw.exe'),
    path.join(process.resourcesPath || '', 'arkhon-app', 'node_modules', 'node-windows', 'bin', 'winsw', 'winsw.exe'),
  ].filter(Boolean);
  return candidates.find((candidate) => fs.existsSync(candidate)) || null;
}

function assertElectronRuntimeWorks(runtime) {
  if (!/\.exe$/i.test(runtime)) return;
  try {
    const stdout = runWithEnv(runtime, ['-e', 'process.exit(process.versions && process.versions.node ? 0 : 2)'], {
      timeout: 10000,
      cwd: ROOT,
      env: { ELECTRON_RUN_AS_NODE: '1' },
    });
    result.commands.push({
      command: runtime,
      args: ['-e', 'process.exit(process.versions && process.versions.node ? 0 : 2)'],
      cwd: ROOT,
      ok: true,
      stdout,
      env: { ELECTRON_RUN_AS_NODE: '1' },
    });
  } catch (_) {
    throw new Error(`runtime_node_missing: ELECTRON_RUN_AS_NODE no pudo ejecutar ${runtime}`);
  }
}

function runWithEnv(command, argsList, options = {}) {
  const env = { ...process.env, ...(options.env || {}) };
  return execFileSync(command, argsList, {
    cwd: options.cwd || ROOT,
    env,
    stdio: 'pipe',
    timeout: options.timeout || 20000,
    windowsHide: true,
  }).toString();
}

function makeServiceDefinitions(runtime) {
  const agentScript = path.join(ROOT, 'index.js');
  if (!fs.existsSync(agentScript)) throw new Error(`agent_package_missing: no existe ${agentScript}`);

  const updaterScript = findUpdaterScript();
  if (!updaterScript && !skipUpdater) {
    throw new Error('updater_package_missing: no se encontro arkhon-updater/index.js. Usa --no-updater solo en dev/test.');
  }

  const definitions = [
    {
      name: 'ARKHON Agent',
      displayName: 'ARKHON Agent',
      description: 'ARKHON Monitor Connector - servicio local offline-first',
      wrapperName: 'winsw-agent.exe',
      script: agentScript,
      workingDirectory: ROOT,
      dataDir: AGENT_DATA_DIR,
      logDir: LOG_DIR,
      env: {
        NODE_ENV: 'production',
        ARKHON_SERVICE: '1',
        CONFIG_PATH: CONFIG_DIR,
        ARKHON_AGENT_DATA_DIR: AGENT_DATA_DIR,
        ARKHON_AGENT_QUEUE_PATH: QUEUE_PATH,
        ARKHON_AGENT_BATCH_STORE_PATH: QUEUE_PATH,
        ARKHON_AGENT_LOG_DIR: LOG_DIR,
        LOW_MEMORY: LOW_MEMORY ? 'true' : 'false',
        ELECTRON_RUN_AS_NODE: '1',
      },
    },
  ];

  if (updaterScript) {
    definitions.push({
      name: 'ARKHON Updater',
      displayName: 'ARKHON Updater',
      description: 'ARKHON Monitor Updater - supervisor local de actualizaciones',
      wrapperName: 'winsw-updater.exe',
      script: updaterScript,
      workingDirectory: path.dirname(updaterScript),
      dataDir: UPDATER_DATA_DIR,
      logDir: UPDATER_LOG_DIR,
      env: {
        NODE_ENV: 'production',
        ARKHON_UPDATER_SERVICE: '1',
        ARKHON_UPDATER_HOST: '127.0.0.1',
        ARKHON_UPDATER_PORT: '34567',
        ARKHON_AGENT_DATA_DIR: AGENT_DATA_DIR,
        ARKHON_UPDATER_DATA_DIR: UPDATER_DATA_DIR,
        ARKHON_UPDATER_LOG_DIR: UPDATER_LOG_DIR,
        CONFIG_PATH: CONFIG_DIR,
        ELECTRON_RUN_AS_NODE: '1',
      },
    });
  }

  return definitions.map((definition) => ({
    ...definition,
    wrapperPath: path.join(SERVICE_DIR, definition.wrapperName),
    xmlPath: path.join(SERVICE_DIR, definition.wrapperName.replace(/\.exe$/i, '.xml')),
    runtime,
  }));
}

function readPackageVersion(packagePath) {
  try {
    return JSON.parse(fs.readFileSync(packagePath, 'utf8')).version || null;
  } catch (_) {
    return null;
  }
}

function validateProgramDataRuntimeVersion(definitions) {
  if (!EXPECTED_VERSION) return;
  const appVersion = readPackageVersion(path.join(ROOT, 'package.json'));
  const updaterDefinition = definitions.find((definition) => definition.name === 'ARKHON Updater');
  const updaterVersion = updaterDefinition ? readPackageVersion(path.join(updaterDefinition.workingDirectory, 'package.json')) : null;
  const errors = [];
  if (appVersion !== EXPECTED_VERSION) errors.push(`programdata_runtime_not_updated: app=${appVersion || 'missing'} expected=${EXPECTED_VERSION}`);
  if (updaterDefinition && updaterVersion !== EXPECTED_VERSION) errors.push(`programdata_runtime_not_updated: updater=${updaterVersion || 'missing'} expected=${EXPECTED_VERSION}`);
  for (const definition of definitions) {
    if (!fs.existsSync(definition.script)) errors.push(`programdata_runtime_not_updated: missing_script=${definition.script}`);
  }
  if (errors.length > 0) {
    recordEvent('programdata_runtime_not_updated', { expectedVersion: EXPECTED_VERSION, appVersion, updaterVersion, errors });
    throw new Error(errors.join('; '));
  }
  recordEvent('programdata_runtime_updated', { expectedVersion: EXPECTED_VERSION, appVersion, updaterVersion });
}

function writeWinSwConfig(definition, winswSource) {
  recordEvent('service_config_start', { service: definition.name, runtime: definition.runtime });
  fs.mkdirSync(SERVICE_DIR, { recursive: true });
  fs.copyFileSync(winswSource, definition.wrapperPath);
  const configSource = `${winswSource}.config`;
  if (fs.existsSync(configSource)) fs.copyFileSync(configSource, `${definition.wrapperPath}.config`);

  const envXml = Object.entries(definition.env)
    .map(([name, value]) => `  <env name="${xmlEscape(name)}" value="${xmlEscape(value)}"/>`)
    .join('\n');
  const xml = [
    '<service>',
    `  <id>${xmlEscape(definition.name)}</id>`,
    `  <name>${xmlEscape(definition.displayName)}</name>`,
    `  <description>${xmlEscape(definition.description)}</description>`,
    `  <executable>${xmlEscape(definition.runtime)}</executable>`,
    '  <arguments>--max-old-space-size=512 "' + xmlEscape(definition.script) + '"</arguments>',
    `  <workingdirectory>${xmlEscape(definition.workingDirectory)}</workingdirectory>`,
    `  <logpath>${xmlEscape(definition.logDir)}</logpath>`,
    '  <log mode="roll-by-size-time">',
    '    <sizeThreshold>10485760</sizeThreshold>',
    '    <pattern>yyyyMMdd</pattern>',
    '    <autoRollAtTime>00:00:00</autoRollAtTime>',
    '    <zipOlderThanNumDays>7</zipOlderThanNumDays>',
    '  </log>',
    '  <stoptimeout>30sec</stoptimeout>',
    '  <onfailure action="restart" delay="5 sec"/>',
    '  <onfailure action="restart" delay="10 sec"/>',
    '  <onfailure action="restart" delay="30 sec"/>',
    envXml,
    '</service>',
    '',
  ].join('\n');
  fs.writeFileSync(definition.xmlPath, xml, 'utf8');
  recordEvent('winsw_xml_written', { service: definition.name, xmlPath: definition.xmlPath, executable: definition.runtime });
  validateXmlExecutable(definition, 'post_write');
}

function createService(definition) {
  if (serviceExists(definition.name)) deleteService(definition.name);
  recordEvent('service_create_start', { service: definition.name, wrapperPath: definition.wrapperPath });
  const created = tryRun('sc.exe', [
    'create',
    definition.name,
    'binPath=',
    `"${definition.wrapperPath}"`,
    'start=',
    'auto',
    'DisplayName=',
    definition.displayName,
  ], { timeout: 15000 });
  if (!created.ok) throw new Error(`service_install_failed: no se pudo crear ${definition.name}: ${created.stderr || created.stdout || created.error.message}`);

  const description = tryRun('sc.exe', ['description', definition.name, definition.description], { timeout: 10000 });
  if (!description.ok) throw new Error(`service_install_failed: no se pudo configurar descripcion de ${definition.name}: ${description.stderr || description.stdout || description.error.message}`);

  const config = tryRun('sc.exe', ['config', definition.name, 'start=', 'auto', 'obj=', 'LocalSystem'], { timeout: 10000 });
  if (!config.ok) throw new Error(`service_install_failed: no se pudo configurar ${definition.name} como LocalSystem/Auto: ${config.stderr || config.stdout || config.error.message}`);

  const failure = tryRun('sc.exe', ['failure', definition.name, 'reset=', '60', 'actions=', 'restart/5000/restart/10000/restart/30000'], { timeout: 10000 });
  if (!failure.ok) throw new Error(`service_install_failed: no se pudo configurar recovery de ${definition.name}: ${failure.stderr || failure.stdout || failure.error.message}`);

  const failureFlag = tryRun('sc.exe', ['failureflag', definition.name, '1'], { timeout: 10000 });
  if (!failureFlag.ok) addWarning(`No se pudo activar failureflag para ${definition.name}: ${failureFlag.stderr || failureFlag.stdout || failureFlag.error.message}`);

  result.createdServices.push({
    name: definition.name,
    wrapperPath: definition.wrapperPath,
    xmlPath: definition.xmlPath,
    runtime: definition.runtime,
    workingDirectory: definition.workingDirectory,
    script: definition.script,
  });
  recordEvent('service_create_done', { service: definition.name, wrapperPath: definition.wrapperPath });
}

function validateBeforeStart(definition) {
  const errors = [];
  if (!isExistingFile(definition.wrapperPath)) errors.push(`winsw_wrapper_missing:${definition.wrapperPath}`);
  if (!fs.existsSync(definition.xmlPath)) errors.push(`winsw_xml_missing:${definition.xmlPath}`);
  if (!isExistingFile(definition.runtime)) errors.push(`runtime_executable_not_found:${definition.runtime}`);
  const xmlExecutable = readXmlExecutable(definition.xmlPath);
  if (!xmlExecutable) errors.push('winsw_xml_executable_missing');
  if (xmlExecutable && !isExistingFile(xmlExecutable)) errors.push(`runtime_executable_not_found:${xmlExecutable}`);
  if (xmlExecutable && isLegacyAppDataRuntime(xmlExecutable)) errors.push(`legacy_appdata_runtime:${xmlExecutable}`);
  if (xmlExecutable && path.normalize(xmlExecutable).toLowerCase() !== path.normalize(definition.runtime).toLowerCase()) {
    errors.push(`winsw_xml_executable_mismatch:${xmlExecutable}`);
  }
  validateXmlArguments(definition, errors);
  const cim = getServiceCim(definition.name);
  if (cim) {
    const pathName = String(cim.PathName || '');
    if (!pathName.toLowerCase().includes(path.basename(definition.wrapperPath).toLowerCase())) {
      errors.push(`service_pathname_mismatch:${pathName}`);
    }
    if (/node-windows/i.test(pathName)) errors.push(`service_pathname_node_windows:${pathName}`);
    if (/arkhonagent\.exe|arkhonupdater\.exe/i.test(String(cim.Name || ''))) {
      errors.push(`legacy_service_name:${cim.Name}`);
    }
  }
  if (errors.length > 0) {
    recordEvent('service_validation_failed', { phase: 'pre_start', service: definition.name, errors });
    throw new Error(`service_validation_failed: ${definition.name}: ${errors.join(', ')}`);
  }
  recordEvent('service_start_preflight_ok', { service: definition.name, wrapperPath: definition.wrapperPath, xmlPath: definition.xmlPath, executable: xmlExecutable });
}

function startService(definition) {
  validateBeforeStart(definition);
  const current = getServiceCim(definition.name);
  if (current && current.State === 'Running') return current;
  recordEvent('service_start_start', { service: definition.name });
  const started = tryRun('sc.exe', ['start', definition.name], { timeout: 20000 });
  if (!started.ok && !String(started.stdout).includes('service has already been started')) {
    addWarning(`sc.exe start ${definition.name} reporto: ${started.stderr || started.stdout || started.error.message}`);
  }
  const running = waitForServiceState(definition.name, 'Running');
  if (running && running.State === 'Running') recordEvent('service_running', { service: definition.name });
  return running;
}

function validateService(definition) {
  const cim = getServiceCim(definition.name);
  const scQuery = getScQuery(definition.name);
  const scFailure = getScFailure(definition.name);
  const errors = [];

  if (!cim) {
    errors.push('Get-CimInstance no encontro el servicio');
  } else {
    if (cim.Name !== definition.name) errors.push(`Name esperado ${definition.name}, recibido ${cim.Name}`);
    if (cim.DisplayName !== definition.displayName) errors.push(`DisplayName esperado ${definition.displayName}, recibido ${cim.DisplayName}`);
    if (cim.StartName !== 'LocalSystem') errors.push(`StartName esperado LocalSystem, recibido ${cim.StartName}`);
    if (cim.StartMode !== 'Auto') errors.push(`StartMode esperado Auto, recibido ${cim.StartMode}`);
    if (cim.State !== 'Running') errors.push(`State esperado Running, recibido ${cim.State}`);
    if (/cmd\.exe|\.bat|\.cmd|\.vbs|wscript|cscript|CurrentVersion\\Run/i.test(String(cim.PathName || ''))) {
      errors.push(`PathName invalido: ${cim.PathName}`);
    }
    if (/node(\.exe)?\s+.*index\.js/i.test(String(cim.PathName || ''))) {
      errors.push(`PathName parece proceso node manual: ${cim.PathName}`);
    }
    if (cim.Name === 'arkhonagent.exe' || cim.Name === 'arkhonupdater.exe') {
      errors.push(`Service Name legacy invalido: ${cim.Name}`);
    }
  }
  try {
    validateXmlExecutable(definition, 'service_validation');
  } catch (error) {
    errors.push(error.message);
  }
  if (!/(STATE|ESTADO)\s+:\s+4\s+RUNNING/i.test(scQuery)) {
    errors.push('sc.exe query no confirma RUNNING');
  }
  if (!/(RESTART|REINICIAR)/i.test(scFailure)) {
    errors.push('sc.exe qfailure no confirma recovery restart');
  }

  const serviceResult = {
    name: definition.name,
    displayName: definition.displayName,
    installed: Boolean(cim),
    running: cim ? cim.State === 'Running' : false,
    state: cim ? cim.State : 'Missing',
    startMode: cim ? cim.StartMode : null,
    startName: cim ? cim.StartName : null,
    pathName: cim ? cim.PathName : null,
    wrapperPath: definition.wrapperPath,
    script: definition.script,
    failureRecovery: /(RESTART|REINICIAR)/i.test(scFailure),
    errors,
  };
  result.services.push(serviceResult);
  for (const error of errors) addError(`${definition.name}: ${error}`);
}

function waitForLocalJson(pathname, port, eventName, timeoutMs) {
  const started = Date.now();
  let lastError = null;
  while (Date.now() - started < timeoutMs) {
    try {
      const payload = requestLocalJson(pathname, port);
      recordEvent(eventName, { port, elapsedMs: Date.now() - started, version: payload.version || payload.installedVersion || null });
      return payload;
    } catch (error) {
      lastError = error;
      sleep(1000);
    }
  }
  addWarning(`${eventName} no confirmado en ${timeoutMs}ms: ${lastError ? lastError.message : 'sin respuesta'}`);
  return null;
}

function requestLocalJson(pathname, port) {
  const url = `http://127.0.0.1:${port}${pathname}`;
  const ps = [
    '$ErrorActionPreference = "Stop"',
    `$response = Invoke-RestMethod "${url}" -TimeoutSec 2`,
    '$response | ConvertTo-Json -Depth 8 -Compress',
  ].join('; ');
  const probe = tryRun('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', ps], { timeout: 5000 });
  if (!probe.ok || !probe.stdout.trim()) {
    throw new Error(probe.stderr || probe.stdout || probe.error.message);
  }
  return JSON.parse(probe.stdout);
}

function capturePreviousServices() {
  for (const name of LEGACY_SERVICE_NAMES) {
    const cim = getServiceCim(name);
    if (cim) result.previousServices.push(cim);
  }
}

function validateLegacyGone() {
  for (const name of ['arkhonagent.exe', 'arkhonupdater.exe']) {
    const legacy = getServiceCim(name);
    if (legacy) addError(`${name}: servicio legacy no debe existir como sustituto`);
  }
}

function main() {
  print('ARKHON - instalar servicios Windows');
  requireAdmin();

  if (skipUpdater) {
    const env = String(process.env.NODE_ENV || '').toLowerCase();
    const allowed = env === 'development' || env === 'test' || process.env.ARKHON_ALLOW_NO_UPDATER === '1';
    if (!allowed) throw new Error('--no-updater solo esta permitido en dev/test.');
    addWarning('--no-updater activo: no se instalara ARKHON Updater.');
  }

  const runtime = resolveRuntimeExecutable();
  assertElectronRuntimeWorks(runtime);
  const winswSource = findWinSwBinary();
  if (!winswSource) throw new Error('winsw_missing: no se encontro winsw.exe empaquetado para crear servicios Windows reales.');

  for (const dir of [AGENT_DATA_DIR, CONFIG_DIR, LOG_DIR, DIAGNOSTICS_DIR, path.dirname(QUEUE_PATH), UPDATER_DATA_DIR, UPDATER_LOG_DIR, SERVICE_DIR]) {
    fs.mkdirSync(dir, { recursive: true });
  }

  capturePreviousServices();
  const definitions = makeServiceDefinitions(runtime);
  validateProgramDataRuntimeVersion(definitions);

  if (forceRepair) {
    for (const name of LEGACY_SERVICE_NAMES) {
      if (deleteService(name)) print(`[OK] Servicio eliminado para reparacion: ${name}`);
    }
    for (const name of LEGACY_SERVICE_NAMES) {
      if (!waitForServiceDeleted(name)) throw new Error(`service_delete_timeout: ${name} no desaparecio despues de sc.exe delete.`);
    }
    sleep(1500);
  } else {
    for (const name of LEGACY_SERVICE_NAMES.filter((item) => !definitions.some((definition) => definition.name === item))) {
      if (deleteService(name)) print(`[OK] Servicio legacy eliminado: ${name}`);
    }
  }

  for (const definition of definitions) {
    if (serviceExists(definition.name) && (forceReinstall || forceRepair)) deleteService(definition.name);
    writeWinSwConfig(definition, winswSource);
    print(`[INFO] Registrando ${definition.name} con sc.exe`);
    createService(definition);
    startService(definition);
    validateService(definition);
  }

  const health = waitForLocalJson('/api/health', 3001, 'express_health_ready', 180000);
  if (!health) addError('post_install_health_failed: /api/health no respondio despues de iniciar ARKHON Agent');
  if (health && EXPECTED_VERSION && health.version !== EXPECTED_VERSION) addError(`post_install_health_failed: version=${health.version || 'missing'} expected=${EXPECTED_VERSION}`);
  waitForLocalJson('/api/health', 3001, 'port_3001_ready', 1000);
  if (definitions.some((definition) => definition.name === 'ARKHON Updater')) {
    const updaterStatus = waitForLocalJson('/updater/status', 34567, 'updater_status_ready', 30000);
    if (!updaterStatus) addError('updater_status_failed: /updater/status no respondio despues de iniciar ARKHON Updater');
    if (updaterStatus && EXPECTED_VERSION && updaterStatus.installedVersion !== EXPECTED_VERSION) addError(`updater_status_failed: installedVersion=${updaterStatus.installedVersion || 'missing'} expected=${EXPECTED_VERSION}`);
  }

  validateLegacyGone();
  result.serviceInstalled = definitions.every((definition) => {
    const service = result.services.find((item) => item.name === definition.name);
    return service && service.installed === true;
  });
  result.serviceRunning = definitions.every((definition) => {
    const service = result.services.find((item) => item.name === definition.name);
    return service && service.running === true && service.startMode === 'Auto' && service.startName === 'LocalSystem';
  });
}

try {
  main();
  finish(result.errors.length === 0 ? 0 : 1);
} catch (error) {
  addError(error.message || String(error));
  finish(1);
}
