/**
 * Container runtime abstraction for NanoClaw.
 * All runtime-specific logic lives here so swapping runtimes means changing one file.
 */
import { execSync } from 'child_process';

import { logger } from './logger.js';

/** The container runtime binary name. */
export const CONTAINER_RUNTIME_BIN = 'docker';

function sanitizeDockerName(value: string): string {
  return value.replace(/[^a-zA-Z0-9_.-]/g, '-');
}

export function getTenantDockerNetworkName(tenantId: string): string {
  return `nanoclaw-tenant-${sanitizeDockerName(tenantId)}`;
}

/** Returns CLI args for a readonly bind mount. */
export function readonlyMountArgs(
  hostPath: string,
  containerPath: string,
): string[] {
  return ['-v', `${hostPath}:${containerPath}:ro`];
}

/** Returns the shell command to stop a container by name. */
export function stopContainer(name: string): string {
  return `${CONTAINER_RUNTIME_BIN} stop ${name}`;
}

export function ensureTenantDockerNetwork(tenantId: string): string {
  const networkName = getTenantDockerNetworkName(tenantId);
  try {
    execSync(`${CONTAINER_RUNTIME_BIN} network inspect ${networkName}`, {
      stdio: 'pipe',
      timeout: 10000,
    });
  } catch {
    execSync(
      `${CONTAINER_RUNTIME_BIN} network create --driver bridge ${networkName}`,
      {
        stdio: 'pipe',
        timeout: 10000,
      },
    );
    logger.info({ tenantId, networkName }, 'Created tenant Docker network');
  }
  return networkName;
}

export function connectContainerToNetwork(
  containerId: string,
  networkName: string,
  aliases: string[] = [],
): void {
  const inspect = execSync(
    `${CONTAINER_RUNTIME_BIN} inspect --format '{{json .NetworkSettings.Networks}}' ${containerId}`,
    {
      stdio: ['pipe', 'pipe', 'pipe'],
      encoding: 'utf-8',
      timeout: 10000,
    },
  ).trim();

  if (inspect && inspect !== '<no value>') {
    try {
      const networks = JSON.parse(inspect) as Record<string, unknown>;
      if (networks[networkName]) return;
    } catch {
      // Fall through and attempt the connection.
    }
  }

  const aliasArgs = aliases
    .filter(Boolean)
    .flatMap((alias) => ['--alias', sanitizeDockerName(alias)]);
  execSync(
    [
      CONTAINER_RUNTIME_BIN,
      'network',
      'connect',
      ...aliasArgs,
      networkName,
      containerId,
    ].join(' '),
    {
      stdio: 'pipe',
      timeout: 10000,
    },
  );
}

/** Ensure the container runtime is running, starting it if needed. */
export function ensureContainerRuntimeRunning(): void {
  try {
    execSync(`${CONTAINER_RUNTIME_BIN} info`, {
      stdio: 'pipe',
      timeout: 10000,
    });
    logger.debug('Container runtime already running');
  } catch (err) {
    logger.error({ err }, 'Failed to reach container runtime');
    console.error(
      '\n╔════════════════════════════════════════════════════════════════╗',
    );
    console.error(
      '║  FATAL: Container runtime failed to start                      ║',
    );
    console.error(
      '║                                                                ║',
    );
    console.error(
      '║  Agents cannot run without a container runtime. To fix:        ║',
    );
    console.error(
      '║  1. Ensure Docker is installed and running                     ║',
    );
    console.error(
      '║  2. Run: docker info                                           ║',
    );
    console.error(
      '║  3. Restart NanoClaw                                           ║',
    );
    console.error(
      '╚════════════════════════════════════════════════════════════════╝\n',
    );
    throw new Error('Container runtime is required but failed to start');
  }
}

/** Kill orphaned NanoClaw containers from previous runs. */
export function cleanupOrphans(): void {
  try {
    const output = execSync(
      `${CONTAINER_RUNTIME_BIN} ps --filter name=nanoclaw- --format '{{.Names}}\t{{.Label "com.docker.compose.project"}}'`,
      { stdio: ['pipe', 'pipe', 'pipe'], encoding: 'utf-8' },
    );
    const orphans = output
      .trim()
      .split('\n')
      .filter(Boolean)
      .map((line) => {
        const [name, composeProject = ''] = line.split('\t');
        return {
          name: name.trim(),
          composeProject: composeProject.trim(),
        };
      })
      .filter((container) => container.name && !container.composeProject)
      .map((container) => container.name);
    for (const name of orphans) {
      try {
        execSync(stopContainer(name), { stdio: 'pipe' });
      } catch {
        /* already stopped */
      }
    }
    if (orphans.length > 0) {
      logger.info(
        { count: orphans.length, names: orphans },
        'Stopped orphaned containers',
      );
    }
  } catch (err) {
    logger.warn({ err }, 'Failed to clean up orphaned containers');
  }
}
