import { exec as execCb } from 'child_process';
import { promisify } from 'util';
import { logger } from '../utils/logger';

const exec = promisify(execCb);

const EXEC_TIMEOUT = 120_000;

function validateName(name: string, label: string): string {
  if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(name)) {
    throw new Error(`Invalid ${label}: ${name}`);
  }
  return name;
}

function validateDuration(value: string): string {
  if (!/^\d+[smhd]$/.test(value)) throw new Error(`Invalid duration: ${value}`);
  return value;
}

function validateTail(value: number): number {
  return Math.floor(Math.max(1, Math.min(value, 5000)));
}

export interface ContainerInfo {
  name: string;
  service: string;
  status: string;
  state: string;
  health: string;
  ports: string;
  createdAt: string;
  exitCode: number;
}

async function execCmd(command: string, cwd: string, timeoutMs = EXEC_TIMEOUT) {
  logger.debug(`[docker] exec: ${command} (cwd: ${cwd})`);
  try {
    return await exec(command, {
      cwd,
      timeout: timeoutMs,
      maxBuffer: 10 * 1024 * 1024,
      env: { ...process.env, COMPOSE_ANSI: 'never' },
    });
  } catch (err: unknown) {
    const error = err as { stdout?: string; stderr?: string; message?: string; killed?: boolean };
    if (error.killed) throw new Error(`Command timed out after ${timeoutMs}ms: ${command}`);
    throw new Error(`Command failed: ${command}\n${error.stderr || error.message}`);
  }
}

function composeCmd(project: string): string {
  return `docker compose -p ${validateName(project, 'project')}`;
}

export async function composeUp(projectDir: string, project: string, services?: string[]) {
  const svc = services?.length ? ` ${services.map((s) => validateName(s, 'service')).join(' ')}` : '';
  const orphanFlag = services?.length ? '' : ' --remove-orphans';
  const { stdout, stderr } = await execCmd(`${composeCmd(project)} up -d${orphanFlag}${svc}`, projectDir);
  return stdout || stderr;
}

export async function composeDown(projectDir: string, project: string, removeVolumes = false) {
  const flags = removeVolumes ? ' -v' : '';
  const { stdout, stderr } = await execCmd(`${composeCmd(project)} down${flags}`, projectDir);
  return stdout || stderr;
}

export async function composeStop(projectDir: string, project: string) {
  const { stdout, stderr } = await execCmd(`${composeCmd(project)} stop`, projectDir);
  return stdout || stderr;
}

export async function composeRestart(projectDir: string, project: string, service?: string) {
  const svc = service ? ` ${validateName(service, 'service')}` : '';
  const { stdout, stderr } = await execCmd(`${composeCmd(project)} restart${svc}`, projectDir);
  return stdout || stderr;
}

export async function composePull(projectDir: string, project: string) {
  const { stdout, stderr } = await execCmd(`${composeCmd(project)} pull`, projectDir, 300_000);
  return stdout || stderr;
}

export async function composeBuild(projectDir: string, project: string) {
  const { stdout, stderr } = await execCmd(`${composeCmd(project)} build`, projectDir, 600_000);
  return stdout || stderr;
}

export async function composePs(projectDir: string, project: string): Promise<ContainerInfo[]> {
  const { stdout } = await execCmd(`${composeCmd(project)} ps --format json`, projectDir);
  if (!stdout.trim()) return [];
  const containers: ContainerInfo[] = [];
  for (const line of stdout.trim().split('\n')) {
    if (!line.trim()) continue;
    try {
      const raw = JSON.parse(line);
      containers.push({
        name: raw.Name || raw.name || '',
        service: raw.Service || raw.service || '',
        status: raw.Status || raw.status || '',
        state: raw.State || raw.state || '',
        health: raw.Health || raw.health || '',
        ports: raw.Ports || raw.ports || '',
        createdAt: raw.CreatedAt || raw.created_at || '',
        exitCode: raw.ExitCode ?? raw.exit_code ?? 0,
      });
    } catch { /* skip */ }
  }
  return containers;
}

export async function composeLogs(projectDir: string, project: string, service?: string, tail = 200, since?: string) {
  const parts = [composeCmd(project), 'logs', '--no-color'];
  if (tail > 0) parts.push(`--tail=${validateTail(tail)}`);
  if (since) parts.push(`--since=${validateDuration(since)}`);
  if (service) parts.push(validateName(service, 'service'));
  const { stdout, stderr } = await execCmd(parts.join(' '), projectDir);
  return stdout || stderr;
}

export async function composeExec(
  projectDir: string, project: string, service: string,
  command: string, timeoutMs = EXEC_TIMEOUT, envVars?: Record<string, string>
) {
  const envFlags = envVars
    ? Object.entries(envVars).map(([k, v]) => `-e ${k}='${v.replace(/'/g, "'\\''")}'`).join(' ') + ' '
    : '';
  const { stdout, stderr } = await execCmd(
    `${composeCmd(project)} exec -T ${envFlags}${validateName(service, 'service')} ${command}`,
    projectDir, timeoutMs
  );
  return stdout || stderr;
}
