defmodule Cmd.Config.MCP do
  @moduledoc false
  alias Cmd.Config.Utils

  @spec run(map(), list(), list()) :: :ok
  def run(opts, [:mcp, :list], _unknown) do
    settings = Settings.new()

    cond do
      opts[:effective] ->
        Settings.MCP.effective_config(settings)
        |> Jason.encode!(pretty: true)
        |> UI.puts()

      opts[:global] ->
        Settings.MCP.get_config(settings, :global)
        |> Jason.encode!(pretty: true)
        |> UI.puts()

      # project scope explicitly via --project
      opts[:project] ->
        # verify project configuration exists in settings
        case Settings.get_project_data(settings, opts[:project]) do
          nil ->
            UI.error("Project not specified or not found")

          _proj_data ->
            # activate project context and print config
            Settings.set_project(opts[:project])

            Settings.MCP.get_config(settings, :project)
            |> Jason.encode!(pretty: true)
            |> UI.puts()
        end

      # default to current project in settings
      true ->
        case Settings.get_selected_project() do
          {:ok, _proj} ->
            Settings.MCP.get_config(settings, :project)
            |> Jason.encode!(pretty: true)
            |> UI.puts()

          {:error, _} ->
            UI.error("Project not specified or not found")
        end
    end
  end

  def run(opts, [:mcp, :check], _unknown) do
    if opts[:project], do: Settings.set_project(opts[:project])
    Services.MCP.start()

    Services.MCP.test()
    |> Jason.encode!(pretty: true)
    |> UI.puts()
  end

  # Unified entry for add, update, remove
  def run(opts, [:mcp, action], args) when action in [:add, :update, :remove] do
    if opts[:project], do: Settings.set_project(opts[:project])

    case Utils.require_key(opts, args, :name, "Server name") do
      {:error, msg} ->
        UI.error(msg)

      {:ok, name} ->
        do_mcp_action(opts, action, name)
    end
  end

  # ----------------------------------------------------------------------------
  # Helpers
  # ----------------------------------------------------------------------------
  @spec build_server_config_from_opts(map()) :: map()
  defp build_server_config_from_opts(opts) do
    %{}
    |> maybe_put("transport", opts[:transport] || "stdio")
    |> maybe_put("command", opts[:command])
    |> maybe_put("args", opts[:arg] || [])
    |> maybe_put("base_url", opts[:base_url])
    |> maybe_put("headers", parse_kv_list(opts[:header] || []))
    |> maybe_put("env", parse_kv_list(opts[:env] || []))
    |> maybe_put("timeout_ms", opts[:timeout_ms])
  end

  @spec maybe_put(map(), String.t(), any()) :: map()
  defp maybe_put(map, _key, nil), do: map
  defp maybe_put(map, key, value), do: Map.put(map, key, value)

  @spec parse_kv_list(nil | [String.t()]) :: map()
  defp parse_kv_list(nil), do: %{}

  defp parse_kv_list(list) when is_list(list) do
    Enum.reduce(list, %{}, fn kv, acc ->
      case String.split(kv, "=", parts: 2) do
        [k, v] -> Map.put(acc, k, v)
        _ -> acc
      end
    end)
  end

  # Internal dispatcher for MCP add/update/remove actions
  defp do_mcp_action(opts, :add, name) do
    raw = build_server_config_from_opts(opts)
    settings = Settings.new()
    scope = if opts[:global], do: :global, else: :project

    case Settings.MCP.add_server(settings, scope, name, raw) do
      {:ok, upd} ->
        %{name => Settings.MCP.list_servers(upd, scope)[name]}
        |> Jason.encode!(pretty: true)
        |> UI.puts()

      {:error, :exists} ->
        UI.error("Server '#{name}' already exists")

      {:error, err} ->
        UI.error(err)
    end
  end

  defp do_mcp_action(opts, :update, name) do
    raw = build_server_config_from_opts(opts)
    settings = Settings.new()
    scope = if opts[:global], do: :global, else: :project

    case Settings.MCP.update_server(settings, scope, name, raw) do
      {:ok, upd} ->
        %{name => Settings.MCP.list_servers(upd, scope)[name]}
        |> Jason.encode!(pretty: true)
        |> UI.puts()

      {:error, :not_found} ->
        UI.error("Server '#{name}' not found")

      {:error, err} ->
        UI.error(err)
    end
  end

  defp do_mcp_action(opts, :remove, name) do
    settings = Settings.new()
    scope = if opts[:global], do: :global, else: :project

    case Settings.MCP.remove_server(settings, scope, name) do
      {:ok, upd} ->
        Settings.MCP.list_servers(upd, scope)
        |> Jason.encode!(pretty: true)
        |> UI.puts()

      {:error, :not_found} ->
        UI.error("Server '#{name}' not found")
    end
  end
end
