import { CliTerseError } from '@alwaysai/alwayscli';
import * as winston from 'winston';
import { TargetHardware } from '../../core/app';
import { ALWAYSAI_SYSTEM_ID } from '../../environment';
import { logger } from '../logger';
import { Spawner } from '../spawner';
import { stringifyError } from '../stringify-error';

export async function buildDockerImage(props: {
  targetHostSpawner: Spawner;
  targetHardware?: string;
  dockerImageTag?: string;
  dockerfilePath?: string;
  pullBaseImage?: boolean;
  runInForeground?: boolean;
  logger?: winston.Logger;
}) {
  const {
    targetHostSpawner,
    targetHardware,
    dockerImageTag,
    dockerfilePath,
    pullBaseImage,
    runInForeground,
    logger
  } = props;

  let args = ['build', '--network=host'];

  if (targetHardware) {
    args = args.concat(['--build-arg', `ALWAYSAI_HW=${targetHardware}`]);
  }

  if (dockerImageTag) {
    args = args.concat(['-t', dockerImageTag]);
  }

  if (dockerfilePath) {
    args = args.concat(['-f', dockerfilePath]);
  }

  if (pullBaseImage) {
    args = args.concat(['--pull']);
  }

  // first run to see build logs
  if (runInForeground) {
    // On Windows, stream to stdout to prevent garbled log output
    if (process.platform === 'win32') {
      const dockerLogs = await targetHostSpawner.runStreaming({
        exe: 'docker',
        args: args.concat(['.']),
        cwd: '.'
      });
      dockerLogs.pipe(process.stdout);
    } else {
      targetHostSpawner.runForegroundSync({
        exe: 'docker',
        args: args.concat(['.']),
        cwd: '.'
      });
    }
  } else {
    // NOTE: Seeing errors with streaming logs to readable stream so sending all-at-once for now.
    // See:
    // * https://github.com/winstonjs/winston/issues/1339
    // * https://github.com/sponja23/winston-stream-wrapper/blob/main/src/index.ts
    const dockerLogs = await targetHostSpawner.run({
      exe: 'docker',
      args: args.concat(['.']),
      cwd: '.'
    });
    logger?.debug(dockerLogs);
  }

  // second run returns an output - docker does not rebuild it but it returns the success message
  const output = await targetHostSpawner.run({
    exe: 'docker',
    args: args.concat(['--quiet', '.']),
    cwd: '.'
  });
  const dockerImageId = output.trim();
  logger?.info(`Built ${dockerImageId}`);
  return dockerImageId;
}

export async function pullDockerImage(props: {
  targetHostSpawner: Spawner;
  dockerImageId: string;
}) {
  const { targetHostSpawner, dockerImageId } = props;
  const dockerArgs: string[] = ['pull', dockerImageId];
  try {
    await targetHostSpawner.run({
      exe: 'docker',
      args: dockerArgs,
      cwd: '.'
    });
  } catch (err) {
    logger.error(stringifyError(err));
    throw new CliTerseError(
      `Pull access denied for ${dockerImageId}, repository does not exist or may require 'docker login'.`
    );
  }
}

export type DockerRunCmd = {
  dockerImageId: string;
  pullImage?: boolean;
  remove?: boolean;
  interactive?: boolean;
  tty?: boolean;
  volumes?: string[];
  env_vars?: string[];
  user?: string;
  ports?: string[];
  targetHardware?: TargetHardware;
  workdir?: string;
  detach?: boolean;
  restart?: string;
  exe?: string;
  exeArgs?: string[];
};

export function getDockerRunCmd(cmd: DockerRunCmd) {
  const args: string[] = ['run', '--privileged'];
  if (ALWAYSAI_SYSTEM_ID) {
    args.push('--env', `ALWAYSAI_SYSTEM_ID=${ALWAYSAI_SYSTEM_ID}`);
  }

  if (cmd.remove) {
    args.push('--rm');
  }
  if (cmd.pullImage) {
    args.push('--pull', 'always');
  }
  if (cmd.interactive) {
    args.push('--interactive');
  }
  if (cmd.tty) {
    args.push('--tty');
  }
  if (cmd.volumes) {
    for (const v of cmd.volumes) {
      args.push('--volume', v);
    }
  }
  if (cmd.env_vars) {
    for (const e of cmd.env_vars) {
      args.push('--env', e);
    }
  }
  if (cmd.user) {
    args.push('--user', cmd.user);
  }
  if (cmd.ports) {
    for (const p of cmd.ports) {
      args.push('--publish', `127.0.0.1:${p}:${p}/tcp`);
    }
  } else {
    args.push('--network=host');
  }
  if (
    cmd.targetHardware?.includes('jetson') ||
    cmd.targetHardware?.includes('x86-trt')
  ) {
    args.push('--runtime=nvidia');
  }
  if (cmd.targetHardware?.includes('jetson')) {
    args.push('--ipc=host');
    args.push('--volume');
    args.push('/tmp/argus_socket:/tmp/argus_socket');
  }
  if (cmd.workdir) {
    args.push('--workdir', cmd.workdir);
  }
  if (cmd.detach) {
    args.push('--detach');
  }
  if (cmd.restart) {
    args.push('--restart', cmd.restart);
  }
  args.push(cmd.dockerImageId);
  if (cmd.exe) {
    args.push(cmd.exe);
    if (cmd.exeArgs) {
      args.push(...cmd.exeArgs);
    }
  }
  return args;
}

export async function runDockerContainer(props: {
  targetHostSpawner: Spawner;
  cmd: DockerRunCmd;
}) {
  const { targetHostSpawner, cmd } = props;
  const dockerArgs = getDockerRunCmd(cmd);
  const output = await targetHostSpawner.run({
    exe: 'docker',
    args: dockerArgs,
    cwd: '.'
  });
  const containerId = output.trim();
  return containerId;
}

export async function runDockerContainerForeground(props: {
  targetHostSpawner: Spawner;
  cmd: DockerRunCmd;
}) {
  const { targetHostSpawner, cmd } = props;
  const dockerArgs = getDockerRunCmd(cmd);
  await targetHostSpawner.runForeground({
    exe: 'docker',
    args: dockerArgs,
    cwd: '.'
  });
}
