defmodule Mix.Tasks.Pow.Phoenix.Install do
  @shortdoc "Prints instructions for setting up Pow with Phoenix"

  @moduledoc """
  Prints instructions fo setting up Pow with Phoenix.

      mix pow.phoenix.install -r MyApp.Repo

      mix pow.phoenix.install -r MyApp.Repo --context-app :my_app

      mix pow.phoenix.install -r MyApp.Repo --templates --extension PowResetPassword

  Templates are only generated when `--templates` argument is provided.

  See `Mix.Tasks.Pow.Phoenix.Gen.Templates` and
  `Mix.Tasks.Pow.Extension.Phoenix.Gen.Templates` for more.

  ## Arguments

    * `--context-app` - app to use for path and module names
    * `--templates` - generate templates
    * `--extension` - extensions to generate templates for
  """
  use Mix.Task

  alias Mix.{Pow, Pow.Phoenix}
  alias Mix.Tasks.Pow.Extension.Phoenix.Gen.Templates, as: PhoenixExtensionTemplatesTask
  alias Mix.Tasks.Pow.Phoenix.Gen.Templates, as: PhoenixTemplatesTask

  @switches [context_app: :string, templates: :boolean, extension: :keep]
  @default_opts [templates: false]
  @mix_task "pow.phoenix.install"

  @impl true
  def run(args), do: run(args, Pow.schema_options_from_args())

  @doc false
  def run(args, schema_opts) do
    Pow.no_umbrella!(@mix_task)
    Pow.ensure_phoenix!(@mix_task, args)

    args
    |> Pow.parse_options(@switches, @default_opts)
    |> parse_structure()
    |> print_shell_instructions(schema_opts)
    |> maybe_run_gen_templates(args)
    |> maybe_run_extensions_gen_templates(args)
  end

  defp parse_structure({config, _parsed, _invalid}) do
    Map.put(config, :structure, Phoenix.parse_structure(config))
  end

  defp print_shell_instructions(%{structure: structure} = config, schema_opts) do
    [
      config_file_injection(structure, schema_opts),
      phoenix_endpoint_file_injection(structure),
      phoenix_router_file_injection(structure)
    ]
    |> Pow.inject_files()
    |> case do
      :ok ->
        Mix.shell().info("Pow has been installed in your Phoenix app!")
        config

      :error ->
        Mix.raise "Couldn't install Pow! Did you run this inside your Phoenix app?"
    end
  end

  defp config_file_injection(structure, schema_opts) do
    file = Path.expand(Keyword.fetch!(Mix.Project.config(), :config_path))

    content =
      """
      config #{inspect(structure.web_app)}, :pow,
        user: #{inspect(structure.context_base)}.#{schema_opts.schema_name},
        repo: #{inspect(structure.context_base)}.Repo
      """

    %{
      file: file,
      injections: [%{
        content: content,
        test: "config #{inspect(structure.web_app)}, :pow",
        needle: "import_config",
        prepend: true
      }],
      instructions:
        """
        Append this to #{Path.relative_to_cwd(file)}:

        #{content}
        """
    }
  end

  defp phoenix_endpoint_file_injection(structure) do
    file = Path.expand("#{structure.web_prefix}/endpoint.ex")
    content = "  plug Pow.Plug.Session, otp_app: #{inspect(structure.web_app)}"

    %{
      file: file,
      injections: [%{
        content: content,
        test: "plug Pow.Plug.Session",
        needle: "plug Plug.Session"
      }],
      instructions:
        """
        Add the `Pow.Plug.Session` plug to #{Path.relative_to_cwd(file)} after the `Plug.Session` plug:

        defmodule #{inspect(structure.web_module)}.Endpoint do
          use Phoenix.Endpoint, otp_app: #{inspect(structure.web_app)}

          # ...

          plug Plug.Session, @session_options
        #{content}
          plug #{inspect(structure.web_module)}.Router
        end
        """
    }
  end

  defp phoenix_router_file_injection(structure) do
    file = Path.expand("#{structure.web_prefix}/router.ex")

    router_use_content = "  use Pow.Phoenix.Router"
    router_scope_content =
      """
        scope "/" do
          pipe_through :browser

          pow_routes()
        end
      """

    %{
      file: file,
      injections: [
        %{
          content: router_use_content,
          test: "use Pow.Phoenix.Router",
          needle: "use #{inspect(structure.web_module)}, :router"
        },
        %{
          content: router_scope_content,
          test: "pow_routes()",
          needle: "scope ",
          prepend: true,
        }
      ],
      instructions:
        """
        Update `#{Path.relative_to_cwd(file)}` with the Pow routes:

        defmodule #{inspect(structure.web_module)}.Router do
          use #{inspect(structure.web_module)}, :router
        #{router_use_content}

          # ... pipelines

        #{router_scope_content}

          # ... routes
        end
        """
    }
  end

  defp maybe_run_gen_templates(%{templates: true} = config, args) do
    PhoenixTemplatesTask.run(args)

    config
  end
  defp maybe_run_gen_templates(config, _args), do: config

  defp maybe_run_extensions_gen_templates(%{templates: true, extension: extensions} = config, args) when extensions != [] do
    PhoenixExtensionTemplatesTask.run(args)

    config
  end
  defp maybe_run_extensions_gen_templates(config, _args), do: config
end
