defmodule Mix.Tasks.Drab.Install do
  use Mix.Task

  @shortdoc "Installs Drab in the current Phoenix project"

  @moduledoc """
  Installs Drab in the current Phoenix application. It does inject lines of code to the existing
  *.ex, *.exs and *.eex files, so it is wise to commit before continuing.

  It assumes that the Phoenix application was generated by `mix phx.new`. In case you have a non
  standard application, it is better to do it with manual install.

  Usage:

      mix drab.install
  """

  @impl true
  def run(_args) do
    validate_phoenix_version()
    app = Mix.Drab.app_name()

    Mix.shell().info("Checking prerequisites for #{inspect(app)}")
    app_html = find_file("lib", "app.html.eex")
    user_socket = find_file("lib", "user_socket.ex")
    config = find_file("config", "config.exs")
    dev_config = find_file("config", "dev.exs")
    app_js = if webpack?(), do: find_file("assets/js", "app.js")

    if Mix.shell().yes?("The installer is going to modify those files. OK to proceed?") do
      update("app.html.eex", app_html)
      update("user_socket.ex", user_socket)
      update("config.exs", config, app)
      update("dev.exs", dev_config)
      update("app.js", app_js)
      if webpack?(), do: update("config.exs - webpack", config, app)

      Mix.shell().info("""
      Drab has been successfully installed in your Phoenix application.

      Now it is time to create your first commander, for example, for PageController:

          mix drab.gen.commander Page
      """)
    end
  end

  defp update(file, path, app \\ nil)

  defp update("app.html.eex", file, _app) do
    inject = "<%= Drab.Client.run(@conn) %>"
    inject_to_file(file, "  </body>", "    #{inject}\n  </body>")
  end

  defp update("user_socket.ex", file, _app) do
    inject = "use Drab.Socket"
    inject_to_file(file, "  use Phoenix.Socket", "  use Phoenix.Socket\n  #{inject}")
  end

  defp update("dev.exs", file, _app) do
    inject = "templates/.*(eex|drab)$"
    inject_to_file(file, "templates/.*(eex)$", inject)
  end

    defp update("app.js", file, _app) do
    inject = "window.__socket = require(\"phoenix\").Socket;"
    add_to_file(file, inject)
  end

  defp update("config.exs", file, app) do
    phoenix = """
    \n# Configures default Drab file extension
    config :phoenix, :template_engines,
      drab: Drab.Live.Engine\n
    """

    drab = """
    # Configures Drab
    config :drab, #{inspect(Mix.Drab.find_endpoint_in_config_exs(app))},
      otp_app: #{inspect(app)}
    """

    unless inject_string_already_there(file, drab) do
      # File.write!(file, phoenix <> drab, [:append])
      logger = "# Configures Elixir's Logger\n"
      inject_to_file(file, logger, drab <> phoenix <> logger)
    end
  end

  defp update("config.exs - webpack", file, app) do
    drab = """
    # Configures Drab for webpack
    config :drab, #{inspect(Mix.Drab.find_endpoint_in_config_exs(app))},
      js_socket_constructor: "window.__socket"
    """

    unless inject_string_already_there(file, drab) do
      logger = "# Configures Elixir's Logger\n"
      inject_to_file(file, logger, drab <> "\n" <> logger)
    end
  end

  defp inject_to_file(file, search_for, replace_with) do
    unless inject_string_already_there(file, replace_with) do
      f = File.read!(file)
      replaced = String.replace(f, search_for, replace_with)

      if replaced == f do
        Mix.shell().error("Installer could not update #{file}. Please install Drab manually.")
      else
        File.write!(file, replaced, [:write])
      end
    end
  end

  defp add_to_file(file, string) do
    unless inject_string_already_there(file, string) do
      f = File.read!(file)
      added = "#{f}\n#{string}"
      File.write!(file, added, [:write])
    end
  end

  defp inject_string_already_there(file, inject) do
    f = File.read!(file)
    contains = String.contains?(f, inject)

    if contains do
      Mix.shell().info("  Drab is already installed in #{file}, skipping.")
    end

    contains
  end

  defp find_file(dir, name) do
    file =
      case Path.wildcard("#{dir}/**/#{name}") do
        [] -> ask_for_file_path(name)
        [filename] -> filename
        names -> choose_file(names, name)
      end

    Mix.shell().info("  #{file}")
    file
  end

  defp ask_for_file_path(name) do
    Mix.shell().error("Can't find #{name}. Please specify the full path.")
    path = String.trim(Mix.shell().prompt(">"))

    if File.exists?(path) do
      path
    else
      ask_for_file_path(name)
    end
  end

  defp choose_file(names, name) do
    Mix.shell().error("Multiple #{name} found, please copy/paste the full path of correct one.")
    # credo:disable-for-next-line
    Mix.shell().info(Enum.join(names, "\n"))
    path = String.trim(Mix.shell().prompt(">"))

    if File.exists?(path) do
      path
    else
      choose_file(names, name)
    end
  end

  defp validate_phoenix_version do
    Application.load(:phoenix)
    unless supported_phoenix?() do
      Mix.raise("""
      Only Phoenix 1.3 and 1.4 are supported with the installer, please proceed with manual install.
      """)
    end
  end

  defp supported_phoenix?() do
    v = to_string(Application.spec(:phoenix, :vsn))
    Regex.match?(~r/^1.[34]/, v)
  end

  defp webpack?() do
    File.exists?(Path.join("assets", "webpack.config.js"))
  end
end
