defmodule SnmpKit.SnmpMgr.MIB.ExtractionTest do
  use ExUnit.Case, async: true

  alias SnmpKit.SnmpLib.MIB.Parser

  # Test the actual extract_mib_mappings private function behavior
  @tag :yecc_required
  test "extract_mib_mappings resolves UBNT-MIB OIDs" do
    mib_path = Path.join(:code.priv_dir(:towerops), "mibs/ubnt/UBNT-MIB")
    {:ok, mib_content} = File.read(mib_path)
    {:ok, parsed} = Parser.parse(mib_content)

    # Simulate state with standard MIBs
    mock_state = %{
      name_to_oid: %{
        "enterprises" => [1, 3, 6, 1, 4, 1],
        "mib-2" => [1, 3, 6, 1, 2, 1],
        "system" => [1, 3, 6, 1, 2, 1, 1]
      }
    }

    # Call the extraction logic manually
    result = extract_mib_mappings_manually(parsed, mock_state, mib_content)

    # Check that new OIDs were resolved
    assert map_size(result.name_to_oid) > map_size(mock_state.name_to_oid),
           "Should have more OIDs after extraction"

    # Check specific UBNT OIDs
    assert Map.has_key?(result.name_to_oid, "ubnt")
    assert Map.has_key?(result.name_to_oid, "ubntMIB")
    assert Map.has_key?(result.name_to_oid, "ubntAFLTU")

    # Verify the resolved values
    assert result.name_to_oid["ubnt"] == [1, 3, 6, 1, 4, 1, 41_112]
    assert result.name_to_oid["ubntMIB"] == [1, 3, 6, 1, 4, 1, 41_112, 1]
    assert result.name_to_oid["ubntAFLTU"] == [1, 3, 6, 1, 4, 1, 41_112, 1, 10]
  end

  # Manually implement the extraction logic for testing
  defp extract_mib_mappings_manually(parsed, state, mib_content) do
    definitions = Map.get(parsed, :definitions, [])

    # Build OID definitions
    oid_defs =
      definitions
      |> Enum.filter(&(Map.get(&1, :__type__) in [:object_type, :object_identifier, :module_identity]))
      |> Enum.reduce(%{}, fn def, acc ->
        name = Map.get(def, :name)
        oid = Map.get(def, :oid)
        parent = Map.get(def, :parent)
        sub_index = Map.get(def, :sub_index)

        cond do
          name && oid -> Map.put(acc, name, oid)
          name && parent && sub_index -> Map.put(acc, name, {String.to_atom(parent), sub_index})
          name && parent -> Map.put(acc, name, {String.to_atom(parent), nil})
          true -> acc
        end
      end)

    # Fix missing indices
    oid_defs = fix_missing_indices(oid_defs, mib_content)

    # Resolve OIDs
    name_to_oid = resolve_all_oids(oid_defs, state.name_to_oid)

    %{name_to_oid: name_to_oid, name_to_meta: %{}}
  end

  defp fix_missing_indices(oid_defs, mib_content) do
    missing =
      Enum.filter(oid_defs, fn
        {_, {_, nil}} -> true
        _ -> false
      end)

    Enum.reduce(missing, oid_defs, fn {name, _}, acc ->
      regex = ~r/#{name}\s+OBJECT\s+IDENTIFIER\s+::=\s+\{\s+(\w+)\s+(\d+)\s+\}/

      case Regex.run(regex, mib_content) do
        [_, parent, index_str] ->
          index = String.to_integer(index_str)
          Map.put(acc, name, {String.to_atom(parent), <<index>>})

        _ ->
          acc
      end
    end)
  end

  defp resolve_all_oids(oid_defs, existing_oids) do
    resolve_iteratively(oid_defs, existing_oids, 10)
  end

  defp resolve_iteratively(_oid_defs, resolved, 0), do: resolved

  defp resolve_iteratively(oid_defs, resolved, iterations_left) do
    {new_resolved, remaining} =
      Enum.reduce(oid_defs, {resolved, %{}}, fn {name, oid_def}, {res_acc, rem_acc} ->
        case resolve_single(oid_def, res_acc) do
          {:ok, oid_list} -> {Map.put(res_acc, name, oid_list), rem_acc}
          {:error, _} -> {res_acc, Map.put(rem_acc, name, oid_def)}
        end
      end)

    if map_size(remaining) == map_size(oid_defs) do
      new_resolved
    else
      resolve_iteratively(remaining, new_resolved, iterations_left - 1)
    end
  end

  defp resolve_single(oid_list, _) when is_list(oid_list) do
    if Enum.all?(oid_list, &is_integer/1), do: {:ok, oid_list}, else: {:error, :not_resolved}
  end

  defp resolve_single({parent_name, index_binary}, resolved) when is_atom(parent_name) and is_binary(index_binary) do
    parent_str = Atom.to_string(parent_name)

    case Map.get(resolved, parent_str) do
      parent_oid when is_list(parent_oid) ->
        index =
          case String.to_charlist(index_binary) do
            [code_point | _] -> code_point
            [] -> :binary.decode_unsigned(index_binary)
          end

        {:ok, parent_oid ++ [index]}

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

  defp resolve_single(_, _), do: {:error, :invalid_format}
end
