defmodule Teex.Pipeline.Install do
  @moduledoc false

  alias Teex.Types.Library
  alias Teex.Types.Workspace
  alias Teex.Types.Error

  alias Teex.Util.Messages
  alias Teex.Util.Tar

  def run(%Library{tarball: nil}, _) do
    {:error, Error.build(type: :install, details: "Empty tarball passed into Install.run/2 pipeline")}
  end

  def run(%Library{} = lib, %Workspace{installed_libraries: current_libs} = target_workspace) do
    if !File.dir?(target_workspace.path) do
      {:error, Error.build(type: :workspace, details: "Workspace directory does not exist")}
    else
      with {:ok, tar_path} <- save_outer_tarball(lib),
      {:ok, inner_tarball} <- extract_inner_tarball(tar_path),
      {:ok, _lib_path} <- install_inner_tarball(target_workspace, lib, inner_tarball),
      :ok <- install_and_compile(target_workspace, lib) do
        lib = struct(lib, tarball: nil)
        {:ok, struct(target_workspace, installed_libraries: [lib | current_libs])}
      else
        error -> error
      end
    end
  end

  defp save_outer_tarball(%Library{} = lib) do
    temp_file = Path.join(System.tmp_dir!(), lib.name <> lib.version <> ".tar")

    with {:ok, file_handle} <- File.open(temp_file, [:write]),
    :ok <- IO.binwrite(file_handle, lib.tarball),
    :ok <- File.close(file_handle) do
      {:ok, temp_file}
    else
      _ -> {:error, Error.build(type: :install, details: "Failed to extract inner tarball from outter tarball")}
    end
  end

  defp extract_inner_tarball(tar_location) do
    Messages.outbox("Extracting inner tarball from outter tarball")
    with {:ok, file_list} <- Tar.extract_tar_to_memory(tar_location),
    {'contents.tar.gz', compressed_content} <- Enum.find(file_list, fn {file, _} -> file == 'contents.tar.gz' end) do
      {:ok, compressed_content}
    else
      _ -> {:error, Error.build(type: :install, details: "Failed to extract inner tarball from outter tarball")}
    end
  end

  def install_inner_tarball(%Workspace{} = workspace, %Library{} = lib, tar_data) do
    lib_dir = Path.join(workspace.path, lib.name)
    |> Path.join(lib.version)
    file_path = Path.join(lib_dir, "contents.tar.gz")

    with :ok <- File.mkdir_p(lib_dir),
    {:ok, file_handle} <- File.open(file_path, [:write]),
    :ok <- IO.binwrite(file_handle, tar_data),
    :ok <- File.close(file_handle) do
      Tar.extract_tar_to_dir(file_path, lib_dir)
    else
      err -> {:error, Error.build(type: :install, details: "Failed to install the inner tarball", data: err)}
    end
  end

  defp install_and_compile(%Workspace{} = workspace, %Library{} = lib) do
    Messages.wrench("Installing #{lib.name} @ #{lib.version} into workspace: #{workspace.name}")

    lib_path = Path.join([workspace.path, lib.name, lib.version])

    Messages.package("Fetching and compiling deps for #{lib.name} @ #{lib.version}")
    System.cmd("mix", ["deps.get"], cd: lib_path, stderr_to_stdout: true)
    System.cmd("mix", ["deps.compile"], cd: lib_path, stderr_to_stdout: true)

    Messages.tools("Compiling #{lib.name} @ #{lib.version}")
    System.cmd("mix", ["compile"], cd: lib_path, stderr_to_stdout: true)

    :ok
  end
end
