defmodule Raxol.Terminal.Sync.System do
  @moduledoc """
  Unified synchronization system for the terminal emulator.
  Handles synchronization between splits, windows, and tabs with different consistency levels.
  """

  use Raxol.Core.Behaviours.BaseManager
  alias Raxol.Core.Runtime.Log
  # Types
  @type sync_id :: String.t()
  @type sync_key :: String.t()
  @type sync_value :: term()
  @type sync_metadata :: %{
          version: non_neg_integer(),
          timestamp: non_neg_integer(),
          source: String.t(),
          consistency: :strong | :eventual | :causal
        }
  @type sync_entry :: %{
          key: sync_key(),
          value: sync_value(),
          metadata: sync_metadata()
        }
  @type sync_stats :: %{
          sync_count: non_neg_integer(),
          conflict_count: non_neg_integer(),
          last_sync: non_neg_integer(),
          consistency_levels: %{atom() => non_neg_integer()}
        }

  # Client API
  def sync(sync_id, key, value, opts \\ []) do
    Log.debug(
      "[System] sync called: sync_id=#{sync_id}, key=#{key}, value=#{inspect(value)}, opts=#{inspect(opts)}"
    )

    GenServer.call(__MODULE__, {:sync, sync_id, key, value, opts})
  end

  def get(sync_id, key) do
    GenServer.call(__MODULE__, {:get, sync_id, key})
  end

  def get_all(sync_id) do
    GenServer.call(__MODULE__, {:get_all, sync_id})
  end

  def delete(sync_id, key) do
    GenServer.call(__MODULE__, {:delete, sync_id, key})
  end

  def clear(sync_id) do
    GenServer.call(__MODULE__, {:clear, sync_id})
  end

  def stats(sync_id) do
    GenServer.call(__MODULE__, {:stats, sync_id})
  end

  # Server Callbacks
  @impl true
  def init_manager(opts) do
    # Convert keyword list to map if needed
    opts_map = if Keyword.keyword?(opts), do: Map.new(opts), else: opts

    state = %{
      # sync_id => %{key => sync_entry}
      syncs: %{},
      # sync_id => sync_stats
      stats: %{},
      consistency_levels:
        Map.get(opts_map, :consistency_levels, %{
          split: :strong,
          window: :strong,
          tab: :eventual
        })
    }

    {:ok, state}
  end

  @impl true
  def handle_manager_call({:sync, sync_id, key, value, opts}, _from, state) do
    # Convert keyword list to map if needed
    opts_map = if Keyword.keyword?(opts), do: Map.new(opts), else: opts

    consistency =
      Map.get(
        opts_map,
        :consistency,
        Map.get(state.consistency_levels, sync_id, :eventual)
      )

    metadata = %{
      version: Map.get(opts_map, :version, System.monotonic_time()),
      timestamp: System.system_time(),
      source: Map.get(opts_map, :source, "unknown"),
      consistency: consistency
    }

    case do_sync(state, sync_id, key, value, metadata) do
      {:ok, new_state} ->
        {:reply, :ok, new_state}

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

  @impl true
  def handle_manager_call({:get, sync_id, key}, _from, state) do
    case get_sync_entry(state, sync_id, key) do
      {:ok, entry} -> {:reply, {:ok, entry.value}, state}
      {:error, reason} -> {:reply, {:error, reason}, state}
    end
  end

  @impl true
  def handle_manager_call({:get_all, sync_id}, _from, state) do
    case Map.get(state.syncs, sync_id) do
      nil -> {:reply, {:error, :not_found}, state}
      sync_data -> {:reply, {:ok, sync_data}, state}
    end
  end

  @impl true
  def handle_manager_call({:delete, sync_id, key}, _from, state) do
    new_state = delete_sync_entry(state, sync_id, key)
    {:reply, :ok, new_state}
  end

  @impl true
  def handle_manager_call({:clear, sync_id}, _from, state) do
    new_state = clear_sync_entries(state, sync_id)
    {:reply, :ok, new_state}
  end

  @impl true
  def handle_manager_call({:stats, sync_id}, _from, state) do
    case Map.get(state.stats, sync_id) do
      nil -> {:reply, {:error, :not_found}, state}
      stats -> {:reply, {:ok, stats}, state}
    end
  end

  # Private Functions
  defp do_sync(state, sync_id, key, value, metadata) do
    case get_sync_entry(state, sync_id, key) do
      {:ok, existing_entry} ->
        handle_existing_sync(
          state,
          sync_id,
          key,
          value,
          metadata,
          existing_entry
        )

      {:error, :not_found} ->
        handle_new_sync(state, sync_id, key, value, metadata)
    end
  end

  defp handle_existing_sync(
         state,
         sync_id,
         key,
         value,
         metadata,
         existing_entry
       ) do
    case resolve_conflict(metadata, existing_entry.metadata) do
      :keep_existing ->
        {:ok, state}

      :use_new ->
        new_state = update_sync_entry(state, sync_id, key, value, metadata)
        {:ok, new_state}

      :conflict ->
        new_state = increment_conflict_count(state, sync_id)
        {:error, :conflict, new_state}
    end
  end

  defp handle_new_sync(state, sync_id, key, value, metadata) do
    new_state = update_sync_entry(state, sync_id, key, value, metadata)
    {:ok, new_state}
  end

  defp resolve_conflict(new_metadata, existing_metadata) do
    case {new_metadata.consistency, existing_metadata.consistency} do
      {:strong, :strong} ->
        case new_metadata.version > existing_metadata.version do
          true -> :use_new
          false -> :keep_existing
        end

      {:strong, _} ->
        :use_new

      {_, :strong} ->
        :keep_existing

      {_, _} ->
        case new_metadata.version > existing_metadata.version do
          true -> :use_new
          false -> :conflict
        end
    end
  end

  defp get_sync_entry(state, sync_id, key) do
    case Map.get(state.syncs, sync_id) do
      nil ->
        {:error, :not_found}

      sync_data ->
        case Map.fetch(sync_data, key) do
          {:ok, entry} -> {:ok, entry}
          :error -> {:error, :not_found}
        end
    end
  end

  defp update_sync_entry(state, sync_id, key, value, metadata) do
    entry = %{key: key, value: value, metadata: metadata}
    sync_data = Map.get(state.syncs, sync_id, %{})
    new_sync_data = Map.put(sync_data, key, entry)
    new_syncs = Map.put(state.syncs, sync_id, new_sync_data)
    new_stats = update_sync_stats(state.stats, sync_id, metadata.consistency)
    %{state | syncs: new_syncs, stats: new_stats}
  end

  defp delete_sync_entry(state, sync_id, key) do
    case Map.get(state.syncs, sync_id) do
      nil ->
        state

      sync_data ->
        new_sync_data = Map.delete(sync_data, key)
        new_syncs = Map.put(state.syncs, sync_id, new_sync_data)
        %{state | syncs: new_syncs}
    end
  end

  defp clear_sync_entries(state, sync_id) do
    new_syncs = Map.delete(state.syncs, sync_id)
    new_stats = Map.delete(state.stats, sync_id)
    %{state | syncs: new_syncs, stats: new_stats}
  end

  defp update_sync_stats(stats, sync_id, consistency) do
    sync_stats =
      Map.get(stats, sync_id, %{
        sync_count: 0,
        conflict_count: 0,
        last_sync: 0,
        consistency_levels: %{strong: 0, eventual: 0, causal: 0}
      })

    new_sync_stats = %{
      sync_stats
      | sync_count: sync_stats.sync_count + 1,
        last_sync: System.monotonic_time(),
        consistency_levels: Map.update(sync_stats.consistency_levels, consistency, 1, &(&1 + 1))
    }

    Map.put(stats, sync_id, new_sync_stats)
  end

  defp increment_conflict_count(state, sync_id) do
    sync_stats = Map.get(state.stats, sync_id)

    new_sync_stats = %{
      sync_stats
      | conflict_count: sync_stats.conflict_count + 1
    }

    %{state | stats: Map.put(state.stats, sync_id, new_sync_stats)}
  end

  @doc """
  Gets the current monotonic time in the specified unit.
  """
  @spec monotonic_time(:millisecond | :microsecond | :nanosecond) :: integer()
  def monotonic_time(unit) do
    System.monotonic_time(unit)
  end

  @doc """
  Gets the current system time in the specified unit.
  """
  @spec system_time(:millisecond | :microsecond | :nanosecond) :: integer()
  def system_time(unit) do
    System.system_time(unit)
  end
end
