defmodule BDS.MCP.AgentConfig do
  @moduledoc false

  @server_name "bDS"

  def add_to_config(agent, opts \\ []) when is_atom(agent) and is_list(opts) do
    home_dir = Keyword.get(opts, :home_dir, System.user_home!())
    config_path = config_path(agent, home_dir)
    command = Keyword.get(opts, :command, default_command(opts))
    args = Keyword.get(opts, :args, default_args(opts))

    with :ok <- ensure_config_dir(config_path),
         {:ok, config} <- read_config(config_path),
         updated <- merge_config(agent, config, command, args),
         :ok <- write_config(config_path, updated) do
      {:ok, %{config_path: config_path, server_name: @server_name}}
    end
  end

  def remove_from_config(agent, opts \\ []) when is_atom(agent) and is_list(opts) do
    home_dir = Keyword.get(opts, :home_dir, System.user_home!())
    config_path = config_path(agent, home_dir)

    with :ok <- ensure_config_dir(config_path),
         {:ok, config} <- read_config(config_path),
         updated <- remove_server_entry(agent, config),
         :ok <- write_config(config_path, updated) do
      {:ok, %{config_path: config_path, server_name: @server_name}}
    end
  end

  def config_path(:claude_code, home_dir), do: Path.join(home_dir, ".claude.json")

  def config_path(:github_copilot, home_dir),
    do: Path.join([home_dir, "Library", "Application Support", "Code", "User", "mcp.json"])

  def packaged_executable_path(install_root, platform) when is_binary(install_root) do
    executable_name =
      case normalize_platform(platform) do
        :windows -> "bds-mcp.bat"
        _other -> "bds-mcp"
      end

    Path.join([install_root, "mcp", "bin", executable_name])
  end

  defp default_command(opts) do
    install_root = Keyword.fetch!(opts, :install_root)
    platform = Keyword.get(opts, :platform, current_platform())
    packaged_executable_path(install_root, platform)
  end

  defp default_args(_opts), do: []

  defp read_config(path) do
    cond do
      File.exists?(path) ->
        with {:ok, contents} <- read_config_file(path),
             {:ok, config} <- Jason.decode(contents) do
          {:ok, config}
        else
          {:error, %Jason.DecodeError{} = reason} -> {:error, {:decode_config, path, reason}}
          {:error, reason} -> {:error, reason}
        end

      true ->
        {:ok, %{}}
    end
  end

  defp ensure_config_dir(config_path) do
    case File.mkdir_p(Path.dirname(config_path)) do
      :ok -> :ok
      {:error, reason} -> {:error, {:create_config_dir, Path.dirname(config_path), reason}}
    end
  end

  defp read_config_file(path) do
    case File.read(path) do
      {:ok, contents} -> {:ok, contents}
      {:error, reason} -> {:error, {:read_config, path, reason}}
    end
  end

  defp write_config(path, config) do
    case File.write(path, Jason.encode!(config, pretty: true)) do
      :ok -> :ok
      {:error, reason} -> {:error, {:write_config, path, reason}}
    end
  end

  defp merge_config(:github_copilot, config, command, args) do
    servers = Map.get(config, "servers", %{})

    Map.put(
      config,
      "servers",
      Map.put(servers, @server_name, %{"type" => "stdio", "command" => command, "args" => args})
    )
  end

  defp merge_config(:claude_code, config, command, args) do
    servers = Map.get(config, "mcpServers", %{})

    Map.put(
      config,
      "mcpServers",
      Map.put(servers, @server_name, %{"command" => command, "args" => args})
    )
  end

  defp remove_server_entry(:github_copilot, config) do
    servers =
      config
      |> Map.get("servers", %{})
      |> Map.delete(@server_name)

    Map.put(config, "servers", servers)
  end

  defp remove_server_entry(:claude_code, config) do
    servers =
      config
      |> Map.get("mcpServers", %{})
      |> Map.delete(@server_name)

    Map.put(config, "mcpServers", servers)
  end

  defp current_platform do
    case :os.type() do
      {:win32, _type} -> :windows
      {:unix, :darwin} -> :macos
      {:unix, _type} -> :linux
    end
  end

  defp normalize_platform(:darwin), do: :macos
  defp normalize_platform(platform), do: platform
end
