import { readFileSync } from 'fs';
import { join } from 'path';

/**
 * Peer Device Mode controller/peer command ownership boundary.
 *
 * The controller-side deny list in the Web UI transport adapter is an
 * optimization: it keeps a controller-owned command on the controller without
 * a round trip. It is not the boundary. A controller running an older build,
 * or any non-Web-UI controller, still reaches a peer host over HostInvoke, so
 * each peer host must independently refuse every controller-owned command.
 *
 * The enforced direction is therefore one-way: whatever the controller refuses
 * to send, a peer host must also refuse to run. A host denying more than the
 * controller is safe and stays allowed.
 */

const FE_ADAPTER = 'src/web-ui/src/infrastructure/api/adapters/peer-device-adapter.ts';
const DESKTOP_HOST = 'src/apps/desktop/src/api/peer_host_invoke.rs';
const CLI_HOST = 'src/apps/cli/src/peer_host/deny.rs';

/**
 * Commands the CLI peer host answers before the deny-list check, so they are
 * intentionally absent from its list. See `src/apps/cli/src/peer_host/dispatch.rs`.
 */
const CLI_PRE_HANDLED_COMMANDS = new Set([
  'peer_control_attach',
  'peer_control_detach',
  'peer_mode_ping',
  'account_cancel_pending_login',
]);

function stripLineComments(text) {
  return text.replace(/\/\/[^\n]*/g, '');
}

function parseTypeScriptSet(source, name) {
  const match = new RegExp(`const ${name}\\s*=\\s*new Set\\(\\[(.*?)\\n\\]\\);`, 's').exec(source);
  if (!match) {
    return null;
  }
  return new Set(Array.from(stripLineComments(match[1]).matchAll(/'([^']+)'/g), m => m[1]));
}

function parseRustSlice(source, name) {
  const match = new RegExp(`static ${name}[^=]*=\\s*&\\[(.*?)\\n\\];`, 's').exec(source);
  if (!match) {
    return null;
  }
  return new Set(Array.from(stripLineComments(match[1]).matchAll(/"([^"]+)"/g), m => m[1]));
}

export function checkPeerCommandPolicySync(root) {
  const failures = [];

  const read = (relativePath) => {
    try {
      return readFileSync(join(root, relativePath), 'utf8');
    } catch {
      failures.push({
        path: relativePath,
        line: 1,
        message:
          'Peer command policy check could not read this file; update scripts/core-boundaries/peer-command-policy.mjs if it moved',
      });
      return null;
    }
  };

  const feSource = read(FE_ADAPTER);
  const desktopSource = read(DESKTOP_HOST);
  const cliSource = read(CLI_HOST);
  if (!feSource || !desktopSource || !cliSource) {
    return failures;
  }

  const controllerDenied = parseTypeScriptSet(feSource, 'LOCAL_ONLY_COMMANDS');
  const desktopDenied = parseRustSlice(desktopSource, 'LOCAL_ONLY_COMMANDS');
  const cliDenied = parseRustSlice(cliSource, 'LOCAL_ONLY_COMMANDS');

  for (const [path, parsed] of [
    [FE_ADAPTER, controllerDenied],
    [DESKTOP_HOST, desktopDenied],
    [CLI_HOST, cliDenied],
  ]) {
    if (!parsed) {
      failures.push({
        path,
        line: 1,
        message:
          'Could not parse LOCAL_ONLY_COMMANDS; keep the declaration shape the peer command policy check expects',
      });
    }
  }
  if (!controllerDenied || !desktopDenied || !cliDenied) {
    return failures;
  }

  const missingOnDesktop = [...controllerDenied].filter(command => !desktopDenied.has(command));
  if (missingOnDesktop.length > 0) {
    failures.push({
      path: DESKTOP_HOST,
      line: 1,
      message:
        `Desktop peer host must refuse every controller-owned command. Missing from LOCAL_ONLY_COMMANDS: ${missingOnDesktop.sort().join(', ')}. ` +
        'An older or non-Web-UI controller can still HostInvoke these onto this peer',
    });
  }

  const missingOnCli = [...controllerDenied].filter(
    command => !cliDenied.has(command) && !CLI_PRE_HANDLED_COMMANDS.has(command),
  );
  if (missingOnCli.length > 0) {
    failures.push({
      path: CLI_HOST,
      line: 1,
      message:
        `CLI peer host must refuse every controller-owned command. Missing from LOCAL_ONLY_COMMANDS: ${missingOnCli.sort().join(', ')}. ` +
        'An older or non-Web-UI controller can still HostInvoke these onto this peer',
    });
  }

  const staleCliExceptions = [...CLI_PRE_HANDLED_COMMANDS].filter(
    command => !controllerDenied.has(command),
  );
  if (staleCliExceptions.length > 0) {
    failures.push({
      path: CLI_HOST,
      line: 1,
      message:
        `Stale CLI pre-handled exception(s) in scripts/core-boundaries/peer-command-policy.mjs: ${staleCliExceptions.sort().join(', ')}. ` +
        'Remove the exception once the controller no longer treats the command as controller-owned',
    });
  }

  return failures;
}