import { execSync } from 'child_process';

/** Must match `container_name` in docker-compose.local.yml */
export const FOURRUNR_DB_CONTAINERS = ['4runr-postgres', '4runr-redis'] as const;

const COMPOSE_PROJECT = '4runr';

function dockerShell(): string | undefined {
  return process.platform === 'win32' ? process.env['COMSPEC'] || 'cmd.exe' : undefined;
}

function dockerExec(command: string, timeoutMs: number = 30000): string {
  return execSync(command, {
    encoding: 'utf-8',
    stdio: 'pipe',
    timeout: timeoutMs,
    ...(dockerShell() ? { shell: dockerShell() } : {}),
  });
}

/**
 * Check if Docker is installed and available
 * Works with both Docker Desktop and Docker Engine
 */
export async function isDockerAvailable(): Promise<boolean> {
  try {
    const result = dockerExec('docker --version', 5000);
    return result.includes('Docker version');
  } catch {
    return false;
  }
}

/**
 * Check if Docker daemon is running
 */
export async function isDockerRunning(): Promise<boolean> {
  try {
    dockerExec('docker info', 5000);
    return true;
  } catch {
    return false;
  }
}

/**
 * Check if a named container is running
 */
export function isContainerRunning(containerName: string): boolean {
  try {
    const state = dockerExec(
      `docker inspect --format="{{.State.Running}}" ${containerName}`,
      5000,
    ).trim();
    return state === 'true';
  } catch {
    return false;
  }
}

/**
 * Check if both 4Runr Postgres and Redis containers are running
 */
export async function areContainersRunning(): Promise<boolean> {
  return FOURRUNR_DB_CONTAINERS.every((name) => isContainerRunning(name));
}

/**
 * Start stopped 4Runr containers by name (watchdog stops them on 4r exit)
 */
export async function startStoppedContainers(): Promise<void> {
  const stopped = FOURRUNR_DB_CONTAINERS.filter((name) => !isContainerRunning(name));
  if (stopped.length === 0) {
    return;
  }

  try {
    dockerExec(`docker start ${stopped.join(' ')}`, 60000);
  } catch {
    // Container may not exist yet — compose up will create it
  }
}

/**
 * Start Docker Compose services
 * @param composeFilePath - Absolute path to docker-compose.yml
 */
export async function startDockerCompose(composeFilePath: string): Promise<void> {
  const cmd = `docker compose -p ${COMPOSE_PROJECT} -f "${composeFilePath}" up -d`;
  execSync(cmd, {
    stdio: 'inherit',
    timeout: 120000,
    ...(dockerShell() ? { shell: dockerShell() } : {}),
  });
}

/**
 * Ensure Postgres + Redis are up: start stopped containers, then compose up if needed.
 */
export async function ensure4RunrStackRunning(composeFilePath: string): Promise<void> {
  await startStoppedContainers();

  if (!(await areContainersRunning())) {
    await startDockerCompose(composeFilePath);
  }

  // Watchdog may have stopped only one container — verify both after compose/start
  await startStoppedContainers();
}

/**
 * Stop Docker Compose services (preserves data volumes)
 * @param composeFilePath - Absolute path to docker-compose.yml
 */
export async function stopDockerCompose(composeFilePath: string): Promise<void> {
  execSync(`docker compose -p ${COMPOSE_PROJECT} -f "${composeFilePath}" stop`, {
    stdio: 'pipe',
    timeout: 30000,
    ...(dockerShell() ? { shell: dockerShell() } : {}),
  });
}

/**
 * Stop 4Runr containers by name (when compose file unavailable)
 */
export async function stop4RunrContainers(): Promise<void> {
  try {
    if (process.platform === 'win32') {
      execSync('docker stop 4runr-postgres 4runr-redis 2>nul', {
        stdio: 'pipe',
        timeout: 30000,
        shell: process.env['COMSPEC'] || 'cmd.exe',
      });
    } else {
      execSync('docker stop 4runr-postgres 4runr-redis 2>/dev/null || true', {
        stdio: 'pipe',
        timeout: 30000,
        shell: '/bin/sh',
      });
    }
  } catch {
    // Ignore errors - containers might not exist
  }
}

/**
 * Wait for a container to become healthy
 * @param containerName - Name of the container to check
 * @param timeoutMs - Maximum time to wait (default: 30 seconds)
 */
export async function waitForHealthy(containerName: string, timeoutMs: number = 30000): Promise<void> {
  const start = Date.now();

  while (Date.now() - start < timeoutMs) {
    try {
      const health = dockerExec(
        `docker inspect --format="{{.State.Health.Status}}" ${containerName}`,
        5000,
      ).trim();

      if (health === 'healthy') {
        return;
      }

      // Also accept containers without health checks that are running
      if (health === '' || health === '<no value>') {
        const running = dockerExec(
          `docker inspect --format="{{.State.Running}}" ${containerName}`,
          5000,
        ).trim();

        if (running === 'true') {
          await sleep(2000);
          return;
        }
      }
    } catch {
      // Container might not exist yet or Docker command failed — keep waiting
    }

    await sleep(1000);
  }

  const logs = getContainerLogs(containerName, 20);
  throw new Error(
    `Container ${containerName} did not become healthy within ${timeoutMs}ms. Recent logs:\n${logs}`,
  );
}

/**
 * Check if a port is in use
 * @param port - Port number to check
 */
export async function isPortInUse(port: number): Promise<boolean> {
  try {
    const result = dockerExec(`docker ps --filter "publish=${port}" --format "{{.Names}}"`, 5000);
    return result.trim().length > 0;
  } catch {
    return false;
  }
}

/**
 * Get container logs
 * @param containerName - Name of the container
 * @param lines - Number of lines to retrieve (default: 50)
 */
export function getContainerLogs(containerName: string, lines: number = 50): string {
  try {
    return dockerExec(`docker logs --tail ${lines} ${containerName}`, 10000);
  } catch (err) {
    return `Failed to retrieve logs: ${err instanceof Error ? err.message : String(err)}`;
  }
}

/**
 * Sleep helper
 */
function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

/**
 * Docker status information
 */
export interface DockerStatus {
  available: boolean;
  running: boolean;
  containersRunning: boolean;
  error?: string;
}

/**
 * Get comprehensive Docker status
 */
export async function getDockerStatus(): Promise<DockerStatus> {
  const status: DockerStatus = {
    available: false,
    running: false,
    containersRunning: false,
  };

  try {
    status.available = await isDockerAvailable();
    if (!status.available) {
      status.error = 'Docker not installed';
      return status;
    }

    status.running = await isDockerRunning();
    if (!status.running) {
      status.error = 'Docker daemon not running';
      return status;
    }

    status.containersRunning = await areContainersRunning();
    return status;
  } catch (err) {
    status.error = err instanceof Error ? err.message : String(err);
    return status;
  }
}
