import { DockerfileParser } from "dockerfile-ast";
import { EOL } from "os";
import { getDockerfileBaseImageName } from "./instruction-parser";
import {
  UpdateDockerfileBaseImageNameErrorCode,
  UpdateDockerfileBaseImageNameResult,
} from "./types";

export { updateDockerfileBaseImageName };

/**
 * Updates the image name of the last from stage, after resolving all aliases
 * @param contents Contents of the Dockerfile to update
 * @param newBaseImageName New base image name Dockerfile contents should be updated to
 */
function updateDockerfileBaseImageName(
  contents: string,
  newBaseImageName: string,
): UpdateDockerfileBaseImageNameResult {
  const dockerfile = DockerfileParser.parse(contents);

  const result = getDockerfileBaseImageName(dockerfile);

  if (result.error) {
    return {
      contents,
      error: {
        code: UpdateDockerfileBaseImageNameErrorCode.BASE_IMAGE_NAME_NOT_FOUND,
      },
    };
  }

  const currentBaseImageName = result.baseImage;

  const fromRanges = dockerfile
    .getFROMs()
    .filter((from) => from.getImage() === currentBaseImageName)
    .map((from) => from.getImageRange()!);

  const argRanges = dockerfile
    .getARGs()
    .filter((arg) => arg.getProperty()?.getValue() === currentBaseImageName)
    .map((arg) => arg.getProperty()?.getValueRange()!);

  const ranges = fromRanges.concat(argRanges);

  if (ranges.length === 0) {
    /**
     * This happens when the image is split over multiple FROM and ARG statements
     * making it difficult to update Dockerfiles that fall into these edge cases.
     * e.g.:
     *    ARG REPO=repo
     *    ARG TAG=tag
     *    FROM ${REPO}:${TAG}
     */
    return {
      contents,
      error: {
        code: UpdateDockerfileBaseImageNameErrorCode.BASE_IMAGE_NAME_FRAGMENTED,
      },
    };
  }

  const lines = contents.split(EOL);

  for (const range of ranges) {
    const lineNumber = range.start.line;
    const start = range.start.character;
    const end = range.end.character;

    const content = lines[lineNumber];
    const updated =
      content.substring(0, start) + newBaseImageName + content.substring(end);
    lines[lineNumber] = updated;
  }

  const updatedContents = lines.join(EOL);
  const updatedDockerfile = DockerfileParser.parse(updatedContents);

  if (
    dockerfile.getInstructions().length !==
    updatedDockerfile.getInstructions().length
  ) {
    return {
      contents,
      error: {
        code: UpdateDockerfileBaseImageNameErrorCode.DOCKERFILE_GENERATION_FAILED,
      },
    };
  }

  return {
    contents: updatedContents,
  };
}
