'use strict';

const { execFile: nodeExecFile } = require('child_process');
const path = require('path');
const { normalizeGitHubGitOrigin } = require('./github-origin');

const CODEX_PLUGIN_ID = 'ecc@ecc';
const OFFICIAL_MARKETPLACE_NAME = 'ecc';
const OFFICIAL_MARKETPLACE_REPO = 'affaan-m/ECC';
const NORMALIZED_OFFICIAL_MARKETPLACE_REPO = OFFICIAL_MARKETPLACE_REPO.toLowerCase();
const MAX_OUTPUT_BYTES = 10 * 1024 * 1024;
const PROVIDER_COMMAND_TIMEOUT_MS = 120 * 1000;

class CodexPluginSetupError extends Error {
  constructor(code, message, details = {}) {
    super(message);
    this.name = 'CodexPluginSetupError';
    this.code = code;
    this.phase = details.phase || 'inventory';
    this.argv = [...(details.argv || [])];
  }
}

function fail(code, message, details) {
  throw new CodexPluginSetupError(code, message, details);
}

function parseJsonObject(stdout, inventoryName, phase = 'inventory') {
  let parsed;
  try {
    parsed = JSON.parse(String(stdout || ''));
  } catch (error) {
    fail(
      `INVALID_${inventoryName.toUpperCase()}_INVENTORY`,
      `Codex ${inventoryName} inventory returned invalid JSON: ${error.message}`,
      { phase }
    );
  }
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
    fail(
      `INVALID_${inventoryName.toUpperCase()}_INVENTORY`,
      `Codex ${inventoryName} inventory is invalid: expected a JSON object`,
      { phase }
    );
  }
  return parsed;
}

function parseMarketplaceInventory(stdout, phase) {
  const inventory = parseJsonObject(stdout, 'marketplace', phase);
  if (!Array.isArray(inventory.marketplaces)) {
    fail(
      'INVALID_MARKETPLACE_INVENTORY',
      'Codex marketplace inventory is invalid: expected `marketplaces` to be an array',
      { phase }
    );
  }
  for (const marketplace of inventory.marketplaces) {
    if (
      !marketplace
      || typeof marketplace.name !== 'string'
      || marketplace.name.length === 0
      || typeof marketplace.root !== 'string'
      || marketplace.root.length === 0
    ) {
      fail(
        'INVALID_MARKETPLACE_INVENTORY',
        'Codex marketplace inventory contains an invalid marketplace entry',
        { phase }
      );
    }
  }
  const eccEntries = inventory.marketplaces.filter(
    marketplace => marketplace.name === OFFICIAL_MARKETPLACE_NAME
  );
  if (eccEntries.length > 1) {
    fail(
      'INVALID_MARKETPLACE_INVENTORY',
      'Codex marketplace inventory contains duplicate `ecc` entries',
      { phase }
    );
  }
  return inventory.marketplaces;
}

function assertPluginEntries(entries, field, phase) {
  if (!Array.isArray(entries)) {
    fail(
      'INVALID_PLUGIN_INVENTORY',
      `Codex plugin inventory is invalid: expected \`${field}\` to be an array`,
      { phase }
    );
  }
  for (const plugin of entries) {
    if (
      !plugin
      || typeof plugin.pluginId !== 'string'
      || plugin.pluginId.length === 0
    ) {
      fail(
        'INVALID_PLUGIN_INVENTORY',
        `Codex plugin inventory contains an invalid \`${field}\` entry`,
        { phase }
      );
    }
    if (plugin.installed !== undefined && typeof plugin.installed !== 'boolean') {
      fail(
        'INVALID_PLUGIN_INVENTORY',
        `Codex plugin inventory contains an invalid \`${field}\` install state`,
        { phase }
      );
    }
    if (plugin.enabled !== undefined && typeof plugin.enabled !== 'boolean') {
      fail(
        'INVALID_PLUGIN_INVENTORY',
        `Codex plugin inventory contains an invalid \`${field}\` enabled state`,
        { phase }
      );
    }
  }
}

function parsePluginInventory(stdout, phase) {
  const inventory = parseJsonObject(stdout, 'plugin', phase);
  assertPluginEntries(inventory.installed, 'installed', phase);
  assertPluginEntries(inventory.available, 'available', phase);
  const eccEntries = inventory.installed.filter(
    plugin => plugin.pluginId === CODEX_PLUGIN_ID
  );
  if (eccEntries.length > 1) {
    fail(
      'INVALID_PLUGIN_INVENTORY',
      `Codex plugin inventory contains duplicate ${CODEX_PLUGIN_ID} entries`,
      { phase }
    );
  }
  return {
    installed: [...inventory.installed],
    available: [...inventory.available],
  };
}

function executeFile(execFile, command, args, options) {
  return new Promise((resolve, reject) => {
    execFile(command, args, options, (error, stdout, stderr) => {
      if (error) {
        if (error.stderr === undefined) error.stderr = stderr;
        if (error.stdout === undefined) error.stdout = stdout;
        reject(error);
        return;
      }
      resolve({ stdout: String(stdout || ''), stderr: String(stderr || '') });
    });
  });
}

function isCommandTimeout(error, killSignal = 'SIGKILL') {
  return error?.code === 'ETIMEDOUT'
    || (error?.killed === true && error?.signal === killSignal);
}

async function runCodexCommand(args, options = {}, dependencies = {}) {
  const command = dependencies.command || options.command || 'codex';
  const execFile = dependencies.execFile || nodeExecFile;
  const argv = [...args];
  const timeoutMs = options.timeoutMs ?? PROVIDER_COMMAND_TIMEOUT_MS;
  const killSignal = 'SIGKILL';
  try {
    return await executeFile(execFile, command, argv, {
      cwd: options.cwd || process.cwd(),
      encoding: 'utf8',
      env: options.env || process.env,
      maxBuffer: MAX_OUTPUT_BYTES,
      killSignal,
      shell: false,
      timeout: timeoutMs,
      windowsHide: true,
    });
  } catch (error) {
    if (isCommandTimeout(error, killSignal)) {
      fail(
        'CODEX_COMMAND_TIMEOUT',
        `Codex command timed out after ${timeoutMs} ms`,
        { argv, phase: options.phase }
      );
    }
    if (error?.code === 'ENOENT') {
      fail(
        'CODEX_NOT_FOUND',
        'Codex CLI is not installed or `codex` is not on PATH. Install Codex, then rerun ECC setup.',
        { argv, phase: options.phase }
      );
    }
    const detail = String(error?.stderr || error?.stdout || error?.message || '').trim();
    fail(
      'CODEX_COMMAND_FAILED',
      `Codex command failed${detail ? `: ${detail}` : ''}`,
      { argv, phase: options.phase }
    );
  }
}

async function resolveMarketplaceRepository(marketplace, options = {}, dependencies = {}) {
  const execFile = dependencies.execFile || nodeExecFile;
  const timeoutMs = options.timeoutMs ?? PROVIDER_COMMAND_TIMEOUT_MS;
  const killSignal = 'SIGKILL';
  let result;
  try {
    result = await executeFile(
      execFile,
      dependencies.gitCommand || 'git',
      ['-C', marketplace.root, 'remote', 'get-url', 'origin'],
      {
        cwd: options.cwd || process.cwd(),
        encoding: 'utf8',
        env: options.env || process.env,
        maxBuffer: MAX_OUTPUT_BYTES,
        killSignal,
        shell: false,
        timeout: timeoutMs,
        windowsHide: true,
      }
    );
  } catch (error) {
    if (isCommandTimeout(error, killSignal)) {
      fail(
        'MARKETPLACE_PROVENANCE_TIMEOUT',
        `Git provenance verification timed out after ${timeoutMs} ms`,
        { phase: options.phase || 'marketplace-provenance' }
      );
    }
    const detail = String(error?.stderr || error?.message || '').trim();
    fail(
      'MARKETPLACE_COLLISION',
      `Refusing the existing \`ecc\` marketplace because its Git provenance could not be verified${detail ? `: ${detail}` : ''}.`,
      { phase: options.phase || 'marketplace-provenance' }
    );
  }
  return String(result.stdout || '').trim();
}

async function assertOfficialMarketplace(
  marketplace,
  options,
  dependencies,
  phase = 'marketplace-provenance'
) {
  if (!marketplace) return;
  const resolveRepository = dependencies.resolveMarketplaceRepository
    || (entry => resolveMarketplaceRepository(
      entry,
      { ...options, phase },
      dependencies
    ));
  let repository;
  try {
    repository = normalizeGitHubGitOrigin(await resolveRepository(marketplace));
  } catch (error) {
    if (error instanceof CodexPluginSetupError) throw error;
    const detail = String(error?.message || error || '').trim();
    fail(
      'MARKETPLACE_COLLISION',
      `Refusing the existing \`ecc\` marketplace because its provenance could not be verified${detail ? `: ${detail}` : ''}.`,
      { phase }
    );
  }
  if (repository !== NORMALIZED_OFFICIAL_MARKETPLACE_REPO) {
    fail(
      'MARKETPLACE_COLLISION',
      'Refusing the existing `ecc` marketplace because it is not the official affaan-m/ECC source.',
      { phase }
    );
  }
}

function normalizeMarketplaceRoot(value) {
  if (typeof value !== 'string' || value.length === 0) return null;
  const isWindowsPath = /^[a-z]:[\\/]/i.test(value) || /^\\\\/.test(value);
  const normalized = isWindowsPath
    ? path.win32.normalize(value)
    : path.posix.normalize(value);
  return isWindowsPath ? normalized.toLowerCase() : normalized;
}

function parseMarketplaceUpgradeResult(stdout, marketplace) {
  const phase = 'marketplace-upgrade';
  const argv = [
    'plugin', 'marketplace', 'upgrade', OFFICIAL_MARKETPLACE_NAME, '--json',
  ];
  let result;
  try {
    result = JSON.parse(String(stdout || ''));
  } catch (error) {
    fail(
      'INVALID_MARKETPLACE_UPGRADE_RESULT',
      `Codex marketplace refresh returned invalid JSON: ${error.message}`,
      { phase, argv }
    );
  }
  const validShape = (
    result
    && typeof result === 'object'
    && !Array.isArray(result)
    && Array.isArray(result.selectedMarketplaces)
    && result.selectedMarketplaces.every(name => typeof name === 'string')
    && Array.isArray(result.upgradedRoots)
    && result.upgradedRoots.every(root => typeof root === 'string' && root.length > 0)
    && Array.isArray(result.errors)
  );
  if (!validShape) {
    fail(
      'INVALID_MARKETPLACE_UPGRADE_RESULT',
      'Codex marketplace refresh returned an invalid result.',
      { phase, argv }
    );
  }
  const expectedRoot = normalizeMarketplaceRoot(marketplace.root);
  const upgradedRoot = result.upgradedRoots.length === 1
    ? normalizeMarketplaceRoot(result.upgradedRoots[0])
    : null;
  if (
    result.errors.length > 0
    || result.selectedMarketplaces.length !== 1
    || result.selectedMarketplaces[0] !== OFFICIAL_MARKETPLACE_NAME
    || upgradedRoot !== expectedRoot
  ) {
    fail(
      'MARKETPLACE_REFRESH_FAILED',
      'Codex did not confirm that the official ECC marketplace was refreshed.',
      { phase, argv }
    );
  }
  return result;
}

function findEccMarketplace(marketplaces) {
  return marketplaces.find(
    marketplace => marketplace.name === OFFICIAL_MARKETPLACE_NAME
  ) || null;
}

function findInstalledEccPlugin(inventory) {
  return inventory.installed.find(
    plugin => plugin.pluginId === CODEX_PLUGIN_ID
  ) || null;
}

async function readMarketplaceInventory(run, phase) {
  const result = await run(
    ['plugin', 'marketplace', 'list', '--json'],
    { phase }
  );
  return parseMarketplaceInventory(result.stdout, phase);
}

async function readPluginInventory(run, phase) {
  const result = await run(['plugin', 'list', '--json'], { phase });
  return parsePluginInventory(result.stdout, phase);
}

async function reconcileCodexPlugin(options = {}, dependencies = {}) {
  const run = (args, details = {}) => runCodexCommand(
    args,
    {
      command: options.command,
      cwd: options.cwd,
      env: options.env,
      phase: details.phase,
    },
    dependencies
  );
  const marketplaces = await readMarketplaceInventory(run, 'marketplace-inventory');
  const plugins = await readPluginInventory(run, 'plugin-inventory');
  const marketplace = findEccMarketplace(marketplaces);
  const installedPlugin = findInstalledEccPlugin(plugins);
  await assertOfficialMarketplace(marketplace, options, dependencies);
  const pluginReady = (
    installedPlugin?.installed === true
    && installedPlugin.enabled === true
  );
  const isReconciled = Boolean(marketplace && pluginReady);

  if (options.dryRun) {
    return {
      action: isReconciled
        ? 'unchanged'
        : (installedPlugin ? 'would-update' : 'would-install'),
      dryRun: true,
      marketplaceAction: marketplace
        ? 'would-upgrade'
        : 'would-add',
      pluginId: CODEX_PLUGIN_ID,
      restartRequired: !isReconciled,
    };
  }

  const marketplaceArgs = marketplace
    ? ['plugin', 'marketplace', 'upgrade', OFFICIAL_MARKETPLACE_NAME, '--json']
    : ['plugin', 'marketplace', 'add', OFFICIAL_MARKETPLACE_REPO, '--json'];
  const marketplaceAction = marketplace ? 'upgraded' : 'added';
  const marketplaceResult = await run(marketplaceArgs, {
    phase: marketplace ? 'marketplace-upgrade' : 'marketplace-add',
  });
  if (marketplace) {
    parseMarketplaceUpgradeResult(marketplaceResult.stdout, marketplace);
  }

  const verifiedMarketplaces = await readMarketplaceInventory(
    run,
    'marketplace-verification'
  );
  if (!findEccMarketplace(verifiedMarketplaces)) {
    fail(
      'MARKETPLACE_VERIFICATION_FAILED',
      'Could not verify the ECC marketplace after reconciliation.',
      { phase: 'marketplace-verification' }
    );
  }
  await assertOfficialMarketplace(
    findEccMarketplace(verifiedMarketplaces),
    options,
    dependencies,
    'marketplace-verification'
  );

  const pluginsAfterMarketplace = marketplace
    ? await readPluginInventory(run, 'plugin-verification')
    : plugins;
  const pluginAfterMarketplace = findInstalledEccPlugin(pluginsAfterMarketplace);
  const pluginReadyAfterMarketplace = (
    pluginAfterMarketplace?.installed === true
    && pluginAfterMarketplace.enabled === true
  );

  if (!pluginReadyAfterMarketplace) {
    await run(
      ['plugin', 'add', CODEX_PLUGIN_ID, '--json'],
      { phase: 'plugin-add' }
    );
  }

  const verifiedPlugins = pluginReadyAfterMarketplace
    ? pluginsAfterMarketplace
    : await readPluginInventory(run, 'plugin-verification');
  const verifiedPlugin = findInstalledEccPlugin(verifiedPlugins);
  if (!(verifiedPlugin?.installed === true && verifiedPlugin.enabled === true)) {
    fail(
      'PLUGIN_VERIFICATION_FAILED',
      `Could not verify ${CODEX_PLUGIN_ID} as installed and enabled after reconciliation.`,
      { phase: 'plugin-verification' }
    );
  }

  return {
    action: installedPlugin ? 'updated' : 'installed',
    marketplaceAction,
    pluginId: CODEX_PLUGIN_ID,
    restartRequired: marketplaceAction === 'upgraded' || !pluginReadyAfterMarketplace,
  };
}

module.exports = {
  CODEX_PLUGIN_ID,
  CodexPluginSetupError,
  OFFICIAL_MARKETPLACE_NAME,
  OFFICIAL_MARKETPLACE_REPO,
  PROVIDER_COMMAND_TIMEOUT_MS,
  executeFile,
  findEccMarketplace,
  findInstalledEccPlugin,
  normalizeGitHubGitOrigin,
  parseMarketplaceInventory,
  parseMarketplaceUpgradeResult,
  parsePluginInventory,
  reconcileCodexPlugin,
  resolveMarketplaceRepository,
  runCodexCommand,
};
