defmodule Raxol.Terminal.Plugin.PluginServer do
  @moduledoc """
  Unified plugin system for the Raxol terminal emulator.
  Handles themes, scripting, and extensions.

  Refactored version with pure functional error handling patterns.
  All try/catch blocks have been replaced with with statements and proper error tuples.
  """

  use Raxol.Core.Behaviours.BaseManager
  alias Raxol.Core.Runtime.Log
  # Types
  @type plugin_id :: String.t()
  @type plugin_type :: :theme | :script | :extension
  @type plugin_state :: %{
          id: plugin_id(),
          type: plugin_type(),
          name: String.t(),
          version: String.t(),
          description: String.t(),
          author: String.t(),
          dependencies: [String.t()],
          config: map(),
          status: :active | :inactive | :error,
          error: String.t() | nil
        }

  # Client API
  # BaseManager provides start_link/1 automatically with name: __MODULE__ as default

  @doc """
  Loads a plugin from a file or directory.
  """
  def load_plugin(path, type, opts \\ []) do
    GenServer.call(__MODULE__, {:load_plugin, path, type, opts})
  end

  @doc """
  Unloads a plugin by ID.
  """
  def unload_plugin(plugin_id) do
    GenServer.call(__MODULE__, {:unload_plugin, plugin_id})
  end

  @doc """
  Gets the state of a plugin.
  """
  def get_plugin_state(plugin_id) do
    GenServer.call(__MODULE__, {:get_plugin_state, plugin_id})
  end

  @doc """
  Gets all loaded plugins.
  """
  def get_plugins(opts \\ []) do
    GenServer.call(__MODULE__, {:get_plugins, opts})
  end

  @doc """
  Updates a plugin's configuration.
  """
  def update_plugin_config(plugin_id, config) do
    GenServer.call(__MODULE__, {:update_plugin_config, plugin_id, config})
  end

  @doc """
  Executes a plugin function.
  """
  def execute_plugin_function(plugin_id, function, args \\ []) do
    GenServer.call(
      __MODULE__,
      {:execute_plugin_function, plugin_id, function, args}
    )
  end

  @doc """
  Reloads a plugin.
  """
  def reload_plugin(plugin_id) do
    GenServer.call(__MODULE__, {:reload_plugin, plugin_id})
  end

  # Server Callbacks
  @impl true
  def init_manager(opts) do
    opts_map = Map.new(opts)

    state = %{
      plugins: %{},
      plugin_paths: Map.get(opts_map, :plugin_paths, []),
      auto_load: Map.get(opts_map, :auto_load, true),
      plugin_config: Map.get(opts_map, :plugin_config, %{})
    }

    auto_load_plugins_if_enabled(state.auto_load, state.plugin_paths)

    {:ok, state}
  end

  @impl true
  def handle_manager_call({:load_plugin, path, type, opts}, _from, state) do
    case do_load_plugin(path, type, opts, state) do
      {:ok, plugin_id, plugin_state} ->
        new_state = put_in(state.plugins[plugin_id], plugin_state)
        {:reply, {:ok, plugin_id}, new_state}

      {:error, reason} ->
        {:reply, {:error, reason}, state}
    end
  end

  @impl true
  def handle_manager_call({:unload_plugin, plugin_id}, _from, state) do
    case do_unload_plugin(plugin_id, state) do
      {:ok, new_state} ->
        {:reply, :ok, new_state}

      {:error, reason} ->
        {:reply, {:error, reason}, state}
    end
  end

  @impl true
  def handle_manager_call({:get_plugin_state, plugin_id}, _from, state) do
    case Map.get(state.plugins, plugin_id) do
      nil -> {:reply, {:error, :plugin_not_found}, state}
      plugin_state -> {:reply, {:ok, plugin_state}, state}
    end
  end

  @impl true
  def handle_manager_call({:get_plugins, opts}, _from, state) do
    plugins = filter_plugins(state.plugins, opts)
    {:reply, {:ok, plugins}, state}
  end

  @impl true
  def handle_manager_call(
        {:update_plugin_config, plugin_id, config},
        _from,
        state
      ) do
    case do_update_plugin_config(plugin_id, config, state) do
      {:ok, new_state} ->
        {:reply, :ok, new_state}

      {:error, reason} ->
        {:reply, {:error, reason}, state}
    end
  end

  @impl true
  def handle_manager_call(
        {:execute_plugin_function, plugin_id, function, args},
        _from,
        state
      ) do
    case Map.get(state.plugins, plugin_id) do
      nil ->
        {:reply, {:error, :plugin_not_found}, state}

      plugin_state ->
        result = execute_function(plugin_state, function, args)
        {:reply, result, state}
    end
  end

  @impl true
  def handle_manager_call({:reload_plugin, plugin_id}, _from, state) do
    case do_reload_plugin(plugin_id, state) do
      {:ok, new_state} ->
        {:reply, :ok, new_state}

      {:error, reason} ->
        {:reply, {:error, reason}, state}

      {:error, reason, new_state} ->
        {:reply, {:error, reason, new_state}, new_state}
    end
  end

  # Private Functions
  defp do_load_plugin(path, type, opts, state) do
    with {:ok, plugin_state} <- load_plugin_state(path, type, opts),
         :ok <- validate_plugin(plugin_state),
         :ok <- check_dependencies(plugin_state, state.plugins) do
      {:ok, plugin_state.id, plugin_state}
    else
      {:error, reason} -> {:error, reason}
    end
  end

  defp do_unload_plugin(plugin_id, state) do
    case Map.get(state.plugins, plugin_id) do
      nil ->
        {:error, :plugin_not_found}

      plugin_state ->
        case cleanup_plugin(plugin_state) do
          :ok ->
            new_plugins = Map.delete(state.plugins, plugin_id)
            {:ok, %{state | plugins: new_plugins}}

          {:error, reason} ->
            {:error, reason}
        end
    end
  end

  defp do_update_plugin_config(plugin_id, config, state) do
    case Map.get(state.plugins, plugin_id) do
      nil ->
        {:error, :plugin_not_found}

      plugin_state ->
        case validate_plugin_config(config) do
          :ok ->
            updated_plugin = %{plugin_state | config: config}
            new_plugins = Map.put(state.plugins, plugin_id, updated_plugin)
            {:ok, %{state | plugins: new_plugins}}

          {:error, reason} ->
            {:error, reason}
        end
    end
  end

  defp do_reload_plugin(plugin_id, state) do
    case Map.get(state.plugins, plugin_id) do
      nil ->
        {:error, :plugin_not_found}

      plugin_state ->
        case reload_plugin_attempt(plugin_state, state.plugins) do
          {:ok, new_plugin_state} ->
            new_plugins = Map.put(state.plugins, plugin_id, new_plugin_state)
            {:ok, %{state | plugins: new_plugins}}

          {:error, reason} ->
            error_plugin_state = %{
              plugin_state
              | status: :error,
                error: inspect(reason)
            }

            new_plugins = Map.put(state.plugins, plugin_id, error_plugin_state)
            new_state = %{state | plugins: new_plugins}
            {:error, :reload_failed, new_state}
        end
    end
  end

  defp reload_plugin_attempt(plugin_state, loaded_plugins) do
    with :ok <- cleanup_plugin(plugin_state),
         {:ok, new_plugin_state} <-
           load_plugin_state(
             plugin_state.path,
             plugin_state.type,
             plugin_state.config
           ),
         :ok <- validate_plugin(new_plugin_state),
         :ok <- check_dependencies(new_plugin_state, loaded_plugins) do
      {:ok, new_plugin_state}
    else
      {:error, reason} -> {:error, reason}
    end
  end

  defp generate_plugin_id(path) do
    {:ok, :crypto.hash(:sha256, path) |> Base.encode16()}
  end

  defp load_plugin_state(path, type, opts) do
    case type do
      :theme -> load_theme_plugin(path, opts)
      :script -> load_script_plugin(path, opts)
      :extension -> load_extension_plugin(path, opts)
      _ -> {:error, :invalid_plugin_type}
    end
  end

  defp validate_plugin(plugin_state) do
    required_fields = [:id, :type, :name, :version, :description, :author]

    case Enum.all?(required_fields, &Map.has_key?(plugin_state, &1)) do
      true -> :ok
      false -> {:error, :invalid_plugin_format}
    end
  end

  defp check_dependencies(plugin_state, loaded_plugins) do
    # Check if all dependencies exist by looking up plugin names
    dependency_satisfied? = fn dep_name ->
      Enum.any?(loaded_plugins, fn {_id, plugin} ->
        plugin.name == dep_name and plugin.status == :active
      end)
    end

    case Enum.all?(plugin_state.dependencies, dependency_satisfied?) do
      true -> :ok
      false -> {:error, :module_not_found}
    end
  end

  defp cleanup_plugin(plugin_state) do
    case plugin_state.type do
      :theme -> cleanup_theme_plugin(plugin_state)
      :script -> cleanup_script_plugin(plugin_state)
      :extension -> cleanup_extension_plugin(plugin_state)
    end
  end

  defp validate_plugin_config(config) do
    case is_map(config) do
      true -> :ok
      false -> {:error, :invalid_config_format}
    end
  end

  defp execute_function(plugin_state, function, args) do
    case plugin_state.type do
      :theme -> execute_theme_function(plugin_state, function, args)
      :script -> execute_script_function(plugin_state, function, args)
      :extension -> execute_extension_function(plugin_state, function, args)
    end
  end

  defp filter_plugins(plugins, opts) do
    plugins
    |> Enum.filter(fn {_id, plugin} ->
      Enum.all?(opts, fn
        {:type, type} -> plugin.type == type
        {:status, status} -> plugin.status == status
        _ -> true
      end)
    end)
    |> Map.new()
  end

  defp load_plugins_from_paths(paths) do
    Enum.each(paths, &process_plugin_path/1)
  end

  defp process_plugin_path(path) do
    case File.ls(path) do
      {:ok, files} -> Enum.each(files, &process_plugin_file(path, &1))
      {:error, reason} -> log_plugin_directory_error(path, reason)
    end
  end

  defp process_plugin_file(directory, file) do
    file_path = Path.join(directory, file)

    with {:ok, type} <- determine_plugin_type(file),
         {:ok, _plugin_id} <- load_plugin(file_path, type) do
      :ok
    else
      {:error, reason} ->
        Log.debug("Skipping #{file}: #{inspect(reason)}")
    end
  end

  defp determine_plugin_type(file) do
    Enum.find_value(
      [
        {".theme.json", :theme},
        {".script.ex", :script},
        {".extension", :extension}
      ],
      {:error, :unknown_type},
      fn {extension, type} ->
        check_file_extension(file, extension, type)
      end
    )
  end

  defp log_plugin_directory_error(path, reason) do
    Log.error("Failed to read plugin directory #{path}: #{inspect(reason)}")
  end

  # Theme Plugin Functions
  defp load_theme_plugin(path, opts) do
    opts = normalize_opts_to_list(opts)

    with {:ok, theme_config} <- load_theme_config(path),
         {:ok, theme_module} <- load_theme_module(path),
         {:ok, plugin_id} <- generate_plugin_id(path) do
      theme_state = %{
        id: plugin_id,
        type: :theme,
        name: Keyword.get(opts, :name, theme_config[:name] || "Unnamed Theme"),
        version: Keyword.get(opts, :version, theme_config[:version] || "1.0.0"),
        description: Keyword.get(opts, :description, theme_config[:description] || ""),
        author: Keyword.get(opts, :author, theme_config[:author] || "Unknown"),
        dependencies: Keyword.get(opts, :dependencies, theme_config[:dependencies] || []),
        config: theme_config,
        status: :active,
        error: nil,
        module: theme_module,
        path: path
      }

      {:ok, theme_state}
    else
      {:error, reason} -> {:error, reason}
    end
  end

  defp load_theme_config(path) do
    config_path = get_theme_config_path(path)

    with {:ok, content} <- File.read(config_path),
         {:ok, config} <- safe_json_decode(content) do
      {:ok, config}
    else
      {:error, reason} -> {:error, reason}
    end
  end

  defp safe_json_decode(content) do
    Raxol.Core.ErrorHandling.safe_call(fn ->
      case Jason.decode(content, keys: :atoms) do
        {:ok, decoded} -> {:ok, decoded}
        {:error, _} -> {:error, :invalid_json}
      end
    end)
    |> case do
      {:ok, result} -> result
      {:error, _} -> {:error, :json_decode_failed}
    end
  end

  defp load_theme_module(path) do
    module_path = get_theme_module_path(path)
    load_module_if_exists(module_path)
  end

  defp safe_load_module(path) do
    with {:ok, content} <- File.read(path),
         {:ok, module} <- safe_compile_module(content) do
      {:ok, module}
    else
      error -> error
    end
  end

  defp safe_compile_module(content) do
    case Code.string_to_quoted(content) do
      {:ok, ast} ->
        case safe_compile_quoted(ast) do
          {:ok, module} -> {:ok, module}
          error -> error
        end

      {:error, _} ->
        {:error, :invalid_code}
    end
  end

  defp safe_compile_quoted(ast) do
    compiled = Code.compile_quoted(ast)

    Raxol.Core.ErrorHandling.safe_call(fn ->
      case compiled do
        [{module, _bin} | _] -> {:ok, module}
        _ -> {:error, :compilation_failed}
      end
    end)
    |> case do
      {:ok, result} ->
        result

      {:error, error} ->
        Log.error("Compilation failed: #{inspect(error)}")
        {:error, :compilation_failed}
    end
  end

  defp cleanup_theme_plugin(plugin_state) do
    case plugin_state.module do
      nil ->
        :ok

      module ->
        safe_call_cleanup(module, plugin_state.config, "theme")
    end
  end

  defp safe_call_cleanup(module, config, plugin_type) do
    call_cleanup_if_exported(module, config, plugin_type)
  end

  defp execute_theme_function(plugin_state, function, args) do
    case plugin_state.module do
      nil ->
        {:error, :module_not_loaded}

      module ->
        safe_execute_function(module, function, args, "theme")
    end
  end

  defp safe_execute_function(module, function, args, plugin_type) do
    execute_function_if_exported(module, function, args, plugin_type)
  end

  # Script Plugin Functions
  defp load_script_plugin(path, opts) do
    opts = normalize_opts(opts)

    case handle_script_path(path) do
      {:ok, script_path} -> load_script_from_file(script_path, opts)
      {:error, reason} -> {:error, reason}
    end
  end

  defp normalize_opts(opts) do
    normalize_opts_format(opts)
  end

  defp handle_script_path(path) do
    case {File.dir?(path), File.exists?(path)} do
      {true, _} -> find_script_in_directory(path)
      {false, false} -> {:error, :file_not_found}
      {false, true} -> {:ok, path}
    end
  end

  defp find_script_in_directory(path) do
    script_file = find_script_file(path)

    validate_script_file(script_file)
  end

  defp find_script_file(path) do
    Enum.find_value(
      ["script.ex", "script.exs"],
      nil,
      fn filename ->
        full_path = Path.join(path, filename)
        check_script_file_exists(full_path)
      end
    )
  end

  defp load_script_from_file(path, opts) do
    with {:ok, script_content} <- File.read(path),
         {:ok, script_module} <- compile_script(script_content),
         {:ok, plugin_id} <- generate_plugin_id(path) do
      script_state = build_script_state(plugin_id, script_module, path, opts)
      {:ok, script_state}
    else
      {:error, reason} -> {:error, reason}
    end
  end

  defp build_script_state(plugin_id, script_module, path, opts) do
    %{
      id: plugin_id,
      type: :script,
      name: Keyword.get(opts, :name, "Unnamed Script"),
      version: Keyword.get(opts, :version, "1.0.1"),
      description: Keyword.get(opts, :description, ""),
      author: Keyword.get(opts, :author, "Unknown"),
      dependencies: Keyword.get(opts, :dependencies, []),
      config: Keyword.get(opts, :config, %{}),
      status: :active,
      error: nil,
      module: script_module,
      path: path
    }
  end

  defp compile_script(content) do
    safe_compile_module(content)
  end

  defp cleanup_script_plugin(plugin_state) do
    case plugin_state.module do
      nil ->
        :ok

      module ->
        safe_call_cleanup(module, plugin_state.config, "script")
    end
  end

  defp execute_script_function(plugin_state, function, args) do
    case plugin_state.module do
      nil ->
        {:error, :module_not_loaded}

      module ->
        safe_execute_function(module, function, args, "script")
    end
  end

  # Extension Plugin Functions
  defp load_extension_plugin(path, opts) do
    opts = normalize_opts_to_list(opts)

    with {:ok, extension_config} <- load_extension_config(path),
         {:ok, extension_module} <- load_extension_module(path),
         {:ok, plugin_id} <- generate_plugin_id(path) do
      extension_state = %{
        id: plugin_id,
        type: :extension,
        name: Keyword.get(opts, :name, "Unnamed Extension"),
        version: Keyword.get(opts, :version, "1.0.1"),
        description: Keyword.get(opts, :description, ""),
        author: Keyword.get(opts, :author, "Unknown"),
        dependencies: Keyword.get(opts, :dependencies, []),
        config: extension_config,
        status: :active,
        error: nil,
        module: extension_module,
        path: path
      }

      {:ok, extension_state}
    else
      {:error, reason} -> {:error, reason}
    end
  end

  defp load_extension_config(path) do
    config_path = get_extension_config_path(path)

    with {:ok, content} <- File.read(config_path),
         {:ok, config} <- safe_json_decode(content) do
      {:ok, config}
    else
      {:error, _reason} ->
        # Default config if file doesn't exist
        {:ok, %{}}
    end
  end

  defp load_extension_module(path) do
    module_path = get_extension_module_path(path)
    load_module_if_exists(module_path)
  end

  defp cleanup_extension_plugin(plugin_state) do
    case plugin_state.module do
      nil ->
        :ok

      module ->
        safe_call_cleanup(module, plugin_state.config, "extension")
    end
  end

  defp execute_extension_function(plugin_state, function, args) do
    case plugin_state.module do
      nil ->
        {:error, :module_not_loaded}

      module ->
        safe_execute_function(module, function, args, "extension")
    end
  end

  # Safe function application helper using functional error handling
  defp safe_apply(module, function, args) do
    Raxol.Core.ErrorHandling.safe_call(fn ->
      apply(module, function, args)
    end)
    |> case do
      {:ok, result} -> {:ok, result}
      {:error, {:exit, reason}} -> {:error, {:exit, reason}}
      {:error, {:throw, thrown}} -> {:error, {:throw, thrown}}
      {:error, error} -> {:error, error}
    end
  end

  ## Helper functions for refactored if statements

  defp auto_load_plugins_if_enabled(true, plugin_paths) do
    load_plugins_from_paths(plugin_paths)
  end

  defp auto_load_plugins_if_enabled(false, _plugin_paths), do: :ok

  defp check_file_extension(file, extension, type) do
    case String.ends_with?(file, extension) do
      true -> type
      false -> nil
    end
  end

  defp get_theme_config_path(path) do
    case File.dir?(path) do
      true -> Path.join(path, "theme.json")
      false -> Path.dirname(path) |> Path.join("theme.json")
    end
  end

  defp get_theme_module_path(path) do
    case File.dir?(path) do
      true -> Path.join(path, "theme.ex")
      false -> Path.dirname(path) |> Path.join("theme.ex")
    end
  end

  defp load_module_if_exists(module_path) do
    case File.exists?(module_path) do
      true -> safe_load_module(module_path)
      false -> {:ok, nil}
    end
  end

  defp call_cleanup_if_exported(module, config, plugin_type) do
    case function_exported?(module, :cleanup, 1) do
      true ->
        Raxol.Core.ErrorHandling.safe_call(fn -> module.cleanup(config) end)
        |> case do
          {:ok, result} ->
            result

          {:error, error} ->
            Log.error("#{plugin_type} cleanup failed: #{inspect(error)}")
            {:error, :cleanup_failed}
        end

      false ->
        :ok
    end
  end

  defp execute_function_if_exported(module, function, args, plugin_type) do
    case function_exported?(module, function, length(args)) do
      true ->
        safe_apply(module, function, args)

      false ->
        Log.warning(
          "Function #{function}/#{length(args)} not exported from #{plugin_type} module"
        )

        {:error, :function_not_exported}
    end
  end

  defp normalize_opts_format(opts) when is_map(opts) do
    Enum.into(opts, [])
  end

  defp normalize_opts_format(opts), do: opts

  defp normalize_opts_to_list(opts) when is_map(opts) do
    Enum.into(opts, [])
  end

  defp normalize_opts_to_list(opts), do: opts

  defp validate_script_file(nil), do: {:error, :script_not_found}
  defp validate_script_file(script_file), do: {:ok, script_file}

  defp check_script_file_exists(full_path) do
    case File.exists?(full_path) do
      true -> full_path
      false -> nil
    end
  end

  defp get_extension_config_path(path) do
    case File.dir?(path) do
      true -> Path.join(path, "extension.json")
      false -> Path.dirname(path) |> Path.join("extension.json")
    end
  end

  defp get_extension_module_path(path) do
    case File.dir?(path) do
      true -> Path.join(path, "extension.ex")
      false -> Path.dirname(path) |> Path.join("extension.ex")
    end
  end
end
