# Responsible for exporting app to Dockerfile.
class DockerExporter
  NL_TAB = "\\\n\t".freeze

  # Constructor.
  # @param user_api [DNAnexusAPI] User API.
  # @param url_provider [#app_url(app)] URL provider.
  def initialize(user_api, url_provider)
    @user_api = user_api
    @url_provider = url_provider
    @app = nil
  end

  # Builds Dockerfile for provided app.
  # @param app [App] App to build Dockerfile for.
  # @return [String] Built Dockerfile.
  def call(app)
    @app = app

    build_dockerfile
  end

  private

  # Builds Dockerfile.
  # @return [String] Build Dockerfile.
  def build_dockerfile
    dockerfile = <<~DOCKER
      # Generated by precisionFDA exporter (v1.0.3) on #{Time.current}
      # The asset download links in this file are valid only for 24h.

      # Exported app: #{@app.name}, revision: #{@app.revision}, authored by: #{@app.user.username}
      # #{@url_provider.app_url(@app)}

      # For more information please consult the app export section in the precisionFDA docs

      # Start with Ubuntu #{@app.release} base image
      FROM ubuntu:#{@app.release}

      # Install default precisionFDA Ubuntu packages
      RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y #{NL_TAB}#{apt_packages}

      # Install default precisionFDA python packages
      RUN pip install #{NL_TAB}#{python_packages}

      # Add DNAnexus repo to apt-get
      RUN /bin/bash -c "echo 'deb http://dnanexus-apt-prod.s3.amazonaws.com/ubuntu #{UBUNTU_CODENAMES[@app.release]}/amd64/' > /etc/apt/sources.list.d/dnanexus.list"
      RUN /bin/bash -c "echo 'deb http://dnanexus-apt-prod.s3.amazonaws.com/ubuntu #{UBUNTU_CODENAMES[@app.release]}/all/' >> /etc/apt/sources.list.d/dnanexus.list"
      RUN curl https://dnanexus-sdk.s3.amazonaws.com/apt/ubuntu-signing-key.gpg | apt-key add -

      #{apt_get_update_install}

      #{download_assets}

      # Download helper executables
      RUN curl https://precision.fda.gov/emit-1.0.tar.gz | tar xzf - -C /usr/bin/ --no-same-owner --no-same-permissions
      RUN curl https://precision.fda.gov/run-1.2.tar.gz | tar xzf - -C /usr/bin/ --no-same-owner --no-same-permissions

      #{app_spec_and_code}

      # Create directory /work and set it to $HOME and CWD
      RUN mkdir -p /work
      ENV HOME=/work
      WORKDIR /work

      # Set entry point to container
      ENTRYPOINT ["/usr/bin/run"]
    DOCKER

    # Remove extra empty lines if any.
    dockerfile.gsub(/\n{2,}/, "\n\n")
  end

  # Returns pre-installed Ubuntu packages depending on Ubuntu release.
  # @return [String] Concatenated for RUN command list of packages.
  def apt_packages
    PRE_INSTALLED_OS_PACKAGES[@app.release].join(" #{NL_TAB}")
  end

  # Returns pre-installed Python packages.
  # @return [String] Concatenated for RUN command list of packages.
  def python_packages
    PRE_INSTALLED_PYTHON_PACKAGES.join(" #{NL_TAB}")
  end

  # Returns apt-related section of Dockerfile depending on selected packages.
  # @return [String] Apt-related section.
  def apt_get_update_install
    @app.packages.present? ? install_additional_packages : update_apt
  end

  # Returns command that will be run to update apt packages.
  # @return [String] Packages update section.
  def update_apt
    <<~APT
      # Update apt-get
      RUN DEBIAN_FRONTEND=noninteractive apt-get update
    APT
  end

  # Returns command that will be used to run update and install selected apt packages.
  # @return [String] Packages update and install section.
  def install_additional_packages
    <<~APT
      # Install app-specific Ubuntu packages
      RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y #{NL_TAB}#{@app.packages.join(" #{NL_TAB}")}
    APT
  end

  # Returns section used for downloading assets.
  # @return [String] Assets download section.
  def download_assets
    return if @app.assets.blank?

    cmds = []

    @app.assets.sort_by { |asset| @app.ordered_assets.find_index(asset.dxid) }.each do |asset|
      url = download_asset(asset)
      tar_opts = asset.gzipped? ? "xzf -" : "xf -"
      cmds << "RUN curl #{url} | tar #{tar_opts} -C / --no-same-owner --no-same-permissions"
    end

    <<~ASSETS
      # Download app assets"
      #{cmds.join("\n")}
    ASSETS
  end

  # Gets asset's download URL from the platform.
  # @param asset [Asset] Asset to get download link for.
  # @return [String] Asset's download link.
  def download_asset(asset)
    @user_api.file_download(
      asset.dxid,
      filename: asset.name,
      project: asset.project,
      preauthenticated: true,
    )["url"]
  end

  # Returns spec and code related section.
  # @return [String] Spec and code related section.
  def app_spec_and_code
    shell_friendly_spec = {
      spec: @app.spec,
      assets: @app.ordered_assets,
      packages: @app.packages,
    }.to_json.shellescape

    shell_friendly_code = { code: @app.code }.to_json.shellescape

    cmds = []
    cmds << "RUN " + ["/bin/bash", "-c", "echo -E #{shell_friendly_spec} > /spec.json"].to_json
    cmds << "RUN " + ["/bin/bash", "-c", "echo -E #{shell_friendly_code} | python -c 'import sys,json; print json.load(sys.stdin)[\"code\"]' > /script.sh"].to_json

    <<~APP_SPEC
      # Write app spec and code to root folder
      #{cmds.join("\n")}
    APP_SPEC
  end
end
