defmodule SnmpKit.SnmpMgr.MIB do
  @moduledoc """
  MIB compilation and symbolic name resolution.

  This module provides MIB compilation using Erlang's :snmpc when available,
  and includes a built-in registry of standard MIB objects for basic operations.
  """

  use GenServer

  alias SnmpKit.SnmpLib.MIB
  alias SnmpKit.SnmpLib.MIB.Parser
  alias SnmpKit.SnmpLib.OID

  require Logger

  @compile {:no_warn_undefined, [:snmpc, :snmp_misc]}

  @standard_mibs %{
    # System group (1.3.6.1.2.1.1)
    "sysDescr" => [1, 3, 6, 1, 2, 1, 1, 1],
    "sysObjectID" => [1, 3, 6, 1, 2, 1, 1, 2],
    "sysUpTime" => [1, 3, 6, 1, 2, 1, 1, 3],
    "sysContact" => [1, 3, 6, 1, 2, 1, 1, 4],
    "sysName" => [1, 3, 6, 1, 2, 1, 1, 5],
    "sysLocation" => [1, 3, 6, 1, 2, 1, 1, 6],
    "sysServices" => [1, 3, 6, 1, 2, 1, 1, 7],

    # Interface group (1.3.6.1.2.1.2)
    "ifNumber" => [1, 3, 6, 1, 2, 1, 2, 1],
    "ifTable" => [1, 3, 6, 1, 2, 1, 2, 2],
    "ifEntry" => [1, 3, 6, 1, 2, 1, 2, 2, 1],
    "ifIndex" => [1, 3, 6, 1, 2, 1, 2, 2, 1, 1],
    "ifDescr" => [1, 3, 6, 1, 2, 1, 2, 2, 1, 2],
    "ifType" => [1, 3, 6, 1, 2, 1, 2, 2, 1, 3],
    "ifMtu" => [1, 3, 6, 1, 2, 1, 2, 2, 1, 4],
    "ifSpeed" => [1, 3, 6, 1, 2, 1, 2, 2, 1, 5],
    "ifPhysAddress" => [1, 3, 6, 1, 2, 1, 2, 2, 1, 6],
    "ifAdminStatus" => [1, 3, 6, 1, 2, 1, 2, 2, 1, 7],
    "ifOperStatus" => [1, 3, 6, 1, 2, 1, 2, 2, 1, 8],
    "ifLastChange" => [1, 3, 6, 1, 2, 1, 2, 2, 1, 9],
    "ifInOctets" => [1, 3, 6, 1, 2, 1, 2, 2, 1, 10],
    "ifInUcastPkts" => [1, 3, 6, 1, 2, 1, 2, 2, 1, 11],
    "ifInNUcastPkts" => [1, 3, 6, 1, 2, 1, 2, 2, 1, 12],
    "ifInDiscards" => [1, 3, 6, 1, 2, 1, 2, 2, 1, 13],
    "ifInErrors" => [1, 3, 6, 1, 2, 1, 2, 2, 1, 14],
    "ifInUnknownProtos" => [1, 3, 6, 1, 2, 1, 2, 2, 1, 15],
    "ifOutOctets" => [1, 3, 6, 1, 2, 1, 2, 2, 1, 16],
    "ifOutUcastPkts" => [1, 3, 6, 1, 2, 1, 2, 2, 1, 17],
    "ifOutNUcastPkts" => [1, 3, 6, 1, 2, 1, 2, 2, 1, 18],
    "ifOutDiscards" => [1, 3, 6, 1, 2, 1, 2, 2, 1, 19],
    "ifOutErrors" => [1, 3, 6, 1, 2, 1, 2, 2, 1, 20],
    "ifOutQLen" => [1, 3, 6, 1, 2, 1, 2, 2, 1, 21],
    "ifSpecific" => [1, 3, 6, 1, 2, 1, 2, 2, 1, 22],

    # Interface Extensions (ifX) group (1.3.6.1.2.1.31)
    "ifXTable" => [1, 3, 6, 1, 2, 1, 31, 1],
    "ifXEntry" => [1, 3, 6, 1, 2, 1, 31, 1, 1],
    "ifName" => [1, 3, 6, 1, 2, 1, 31, 1, 1, 1],
    "ifInMulticastPkts" => [1, 3, 6, 1, 2, 1, 31, 1, 1, 2],
    "ifInBroadcastPkts" => [1, 3, 6, 1, 2, 1, 31, 1, 1, 3],
    "ifOutMulticastPkts" => [1, 3, 6, 1, 2, 1, 31, 1, 1, 4],
    "ifOutBroadcastPkts" => [1, 3, 6, 1, 2, 1, 31, 1, 1, 5],
    "ifHCInOctets" => [1, 3, 6, 1, 2, 1, 31, 1, 1, 6],
    "ifHCInUcastPkts" => [1, 3, 6, 1, 2, 1, 31, 1, 1, 7],
    "ifHCInMulticastPkts" => [1, 3, 6, 1, 2, 1, 31, 1, 1, 8],
    "ifHCInBroadcastPkts" => [1, 3, 6, 1, 2, 1, 31, 1, 1, 9],
    "ifHCOutOctets" => [1, 3, 6, 1, 2, 1, 31, 1, 1, 10],
    "ifHCOutUcastPkts" => [1, 3, 6, 1, 2, 1, 31, 1, 1, 11],
    "ifHCOutMulticastPkts" => [1, 3, 6, 1, 2, 1, 31, 1, 1, 12],
    "ifHCOutBroadcastPkts" => [1, 3, 6, 1, 2, 1, 31, 1, 1, 13],
    "ifLinkUpDownTrapEnable" => [1, 3, 6, 1, 2, 1, 31, 1, 1, 14],
    "ifHighSpeed" => [1, 3, 6, 1, 2, 1, 31, 1, 1, 15],
    "ifPromiscuousMode" => [1, 3, 6, 1, 2, 1, 31, 1, 1, 16],
    "ifConnectorPresent" => [1, 3, 6, 1, 2, 1, 31, 1, 1, 17],
    "ifAlias" => [1, 3, 6, 1, 2, 1, 31, 1, 1, 18],
    "ifCounterDiscontinuityTime" => [1, 3, 6, 1, 2, 1, 31, 1, 1, 19],

    # IP group (1.3.6.1.2.1.4)
    "ipForwarding" => [1, 3, 6, 1, 2, 1, 4, 1],
    "ipDefaultTTL" => [1, 3, 6, 1, 2, 1, 4, 2],
    "ipInReceives" => [1, 3, 6, 1, 2, 1, 4, 3],
    "ipInHdrErrors" => [1, 3, 6, 1, 2, 1, 4, 4],
    "ipInAddrErrors" => [1, 3, 6, 1, 2, 1, 4, 5],

    # SNMP group (1.3.6.1.2.1.11)
    "snmpInPkts" => [1, 3, 6, 1, 2, 1, 11, 1],
    "snmpOutPkts" => [1, 3, 6, 1, 2, 1, 11, 2],
    "snmpInBadVersions" => [1, 3, 6, 1, 2, 1, 11, 3],
    "snmpInBadCommunityNames" => [1, 3, 6, 1, 2, 1, 11, 4],
    "snmpInBadCommunityUses" => [1, 3, 6, 1, 2, 1, 11, 5],
    "snmpInASNParseErrs" => [1, 3, 6, 1, 2, 1, 11, 6],
    "snmpInTooBigs" => [1, 3, 6, 1, 2, 1, 11, 8],
    "snmpInNoSuchNames" => [1, 3, 6, 1, 2, 1, 11, 9],
    "snmpInBadValues" => [1, 3, 6, 1, 2, 1, 11, 10],
    "snmpInReadOnlys" => [1, 3, 6, 1, 2, 1, 11, 11],
    "snmpInGenErrs" => [1, 3, 6, 1, 2, 1, 11, 12],
    "snmpInTotalReqVars" => [1, 3, 6, 1, 2, 1, 11, 13],
    "snmpInTotalSetVars" => [1, 3, 6, 1, 2, 1, 11, 14],
    "snmpInGetRequests" => [1, 3, 6, 1, 2, 1, 11, 15],
    "snmpInGetNexts" => [1, 3, 6, 1, 2, 1, 11, 16],
    "snmpInSetRequests" => [1, 3, 6, 1, 2, 1, 11, 17],
    "snmpInGetResponses" => [1, 3, 6, 1, 2, 1, 11, 18],
    "snmpInTraps" => [1, 3, 6, 1, 2, 1, 11, 19],
    "snmpOutTooBigs" => [1, 3, 6, 1, 2, 1, 11, 20],
    "snmpOutNoSuchNames" => [1, 3, 6, 1, 2, 1, 11, 21],
    "snmpOutBadValues" => [1, 3, 6, 1, 2, 1, 11, 22],
    "snmpOutGenErrs" => [1, 3, 6, 1, 2, 1, 11, 24],
    "snmpOutGetRequests" => [1, 3, 6, 1, 2, 1, 11, 25],
    "snmpOutGetNexts" => [1, 3, 6, 1, 2, 1, 11, 26],
    "snmpOutSetRequests" => [1, 3, 6, 1, 2, 1, 11, 27],
    "snmpOutGetResponses" => [1, 3, 6, 1, 2, 1, 11, 28],
    "snmpOutTraps" => [1, 3, 6, 1, 2, 1, 11, 29],
    "snmpEnableAuthenTraps" => [1, 3, 6, 1, 2, 1, 11, 30],

    # Common group prefixes for bulk walking
    "system" => [1, 3, 6, 1, 2, 1, 1],
    "interfaces" => [1, 3, 6, 1, 2, 1, 2],
    "if" => [1, 3, 6, 1, 2, 1, 2],
    "ifX" => [1, 3, 6, 1, 2, 1, 31],
    "ip" => [1, 3, 6, 1, 2, 1, 4],
    "icmp" => [1, 3, 6, 1, 2, 1, 5],
    "tcp" => [1, 3, 6, 1, 2, 1, 6],
    "udp" => [1, 3, 6, 1, 2, 1, 7],
    "snmp" => [1, 3, 6, 1, 2, 1, 11],
    "mib-2" => [1, 3, 6, 1, 2, 1],
    "mgmt" => [1, 3, 6, 1, 2],
    "internet" => [1, 3, 6, 1],

    # Common enterprise OIDs
    "enterprises" => [1, 3, 6, 1, 4, 1],
    "cisco" => [1, 3, 6, 1, 4, 1, 9],
    "hp" => [1, 3, 6, 1, 4, 1, 11],
    "3com" => [1, 3, 6, 1, 4, 1, 43],
    "sun" => [1, 3, 6, 1, 4, 1, 42],
    "dec" => [1, 3, 6, 1, 4, 1, 36],
    "ibm" => [1, 3, 6, 1, 4, 1, 2],
    "microsoft" => [1, 3, 6, 1, 4, 1, 311],
    "netapp" => [1, 3, 6, 1, 4, 1, 789],
    "juniper" => [1, 3, 6, 1, 4, 1, 2636],
    "fortinet" => [1, 3, 6, 1, 4, 1, 12_356],
    "paloalto" => [1, 3, 6, 1, 4, 1, 25_461],
    "mikrotik" => [1, 3, 6, 1, 4, 1, 14_988],

    # Cable/DOCSIS industry OIDs
    "cablelabs" => [1, 3, 6, 1, 4, 1, 4491],
    "docsis" => [1, 3, 6, 1, 2, 1, 127],
    "cableDataPrivateMib" => [1, 3, 6, 1, 4, 1, 4491, 2, 1],
    "arris" => [1, 3, 6, 1, 4, 1, 4115],
    "motorola" => [1, 3, 6, 1, 4, 1, 1166],
    "scientificatlanta" => [1, 3, 6, 1, 4, 1, 1429],
    "broadcom" => [1, 3, 6, 1, 4, 1, 4413]
  }

  # Curated minimal metadata for high-value IF-MIB objects (stopgap until full compiler integration)
  @curated_syntax %{
    # SNMPv2-MIB system group
    "sysDescr" => %{base: :octet_string, textual_convention: "DisplayString", display_hint: "255a"},
    "sysObjectID" => %{base: :object_identifier, textual_convention: nil, display_hint: nil},
    "sysUpTime" => %{base: :timeticks, textual_convention: nil, display_hint: nil},
    "sysContact" => %{base: :octet_string, textual_convention: "DisplayString", display_hint: "255a"},
    "sysName" => %{base: :octet_string, textual_convention: "DisplayString", display_hint: "255a"},
    "sysLocation" => %{base: :octet_string, textual_convention: "DisplayString", display_hint: "255a"},
    "sysServices" => %{base: :integer, textual_convention: nil, display_hint: nil},

    # IF-MIB ifTable (1.3.6.1.2.1.2.2.1)
    "ifIndex" => %{base: :integer, textual_convention: nil, display_hint: nil},
    "ifDescr" => %{base: :octet_string, textual_convention: "DisplayString", display_hint: "255a"},
    "ifType" => %{base: :integer, textual_convention: "IANAifType", display_hint: nil},
    "ifMtu" => %{base: :integer, textual_convention: nil, display_hint: nil},
    "ifSpeed" => %{base: :gauge32, textual_convention: nil, display_hint: nil},
    "ifPhysAddress" => %{base: :octet_string, textual_convention: "PhysAddress", display_hint: nil},
    "ifAdminStatus" => %{base: :integer, textual_convention: nil, display_hint: nil},
    "ifOperStatus" => %{base: :integer, textual_convention: nil, display_hint: nil},
    "ifLastChange" => %{base: :timeticks, textual_convention: nil, display_hint: nil},
    "ifInOctets" => %{base: :counter32, textual_convention: nil, display_hint: nil},
    "ifInUcastPkts" => %{base: :counter32, textual_convention: nil, display_hint: nil},
    "ifInNUcastPkts" => %{base: :counter32, textual_convention: nil, display_hint: nil},
    "ifInDiscards" => %{base: :counter32, textual_convention: nil, display_hint: nil},
    "ifInErrors" => %{base: :counter32, textual_convention: nil, display_hint: nil},
    "ifInUnknownProtos" => %{base: :counter32, textual_convention: nil, display_hint: nil},
    "ifOutOctets" => %{base: :counter32, textual_convention: nil, display_hint: nil},
    "ifOutUcastPkts" => %{base: :counter32, textual_convention: nil, display_hint: nil},
    "ifOutNUcastPkts" => %{base: :counter32, textual_convention: nil, display_hint: nil},
    "ifOutDiscards" => %{base: :counter32, textual_convention: nil, display_hint: nil},
    "ifOutErrors" => %{base: :counter32, textual_convention: nil, display_hint: nil},
    "ifOutQLen" => %{base: :gauge32, textual_convention: nil, display_hint: nil},
    "ifSpecific" => %{base: :object_identifier, textual_convention: nil, display_hint: nil},

    # IF-MIB ifXTable (1.3.6.1.2.1.31.1.1.1)
    "ifName" => %{base: :octet_string, textual_convention: "DisplayString", display_hint: "255a"},
    "ifInMulticastPkts" => %{base: :counter32, textual_convention: nil, display_hint: nil},
    "ifInBroadcastPkts" => %{base: :counter32, textual_convention: nil, display_hint: nil},
    "ifOutMulticastPkts" => %{base: :counter32, textual_convention: nil, display_hint: nil},
    "ifOutBroadcastPkts" => %{base: :counter32, textual_convention: nil, display_hint: nil},
    "ifHCInOctets" => %{base: :counter64, textual_convention: nil, display_hint: nil},
    "ifHCInUcastPkts" => %{base: :counter64, textual_convention: nil, display_hint: nil},
    "ifHCInMulticastPkts" => %{base: :counter64, textual_convention: nil, display_hint: nil},
    "ifHCInBroadcastPkts" => %{base: :counter64, textual_convention: nil, display_hint: nil},
    "ifHCOutOctets" => %{base: :counter64, textual_convention: nil, display_hint: nil},
    "ifHCOutUcastPkts" => %{base: :counter64, textual_convention: nil, display_hint: nil},
    "ifHCOutMulticastPkts" => %{base: :counter64, textual_convention: nil, display_hint: nil},
    "ifHCOutBroadcastPkts" => %{base: :counter64, textual_convention: nil, display_hint: nil},
    "ifLinkUpDownTrapEnable" => %{base: :integer, textual_convention: nil, display_hint: nil},
    "ifHighSpeed" => %{base: :gauge32, textual_convention: nil, display_hint: nil},
    "ifPromiscuousMode" => %{base: :boolean, textual_convention: "TruthValue", display_hint: nil},
    "ifConnectorPresent" => %{base: :boolean, textual_convention: "TruthValue", display_hint: nil},
    "ifAlias" => %{base: :octet_string, textual_convention: "DisplayString", display_hint: "255a"},
    "ifCounterDiscontinuityTime" => %{base: :timeticks, textual_convention: "TimeStamp", display_hint: nil},

    # IP-MIB (ARP table: ipNetToMediaTable 1.3.6.1.2.1.4.22)
    "ipNetToMediaIfIndex" => %{base: :integer, textual_convention: nil, display_hint: nil},
    "ipNetToMediaPhysAddress" => %{base: :octet_string, textual_convention: "PhysAddress", display_hint: nil},
    "ipNetToMediaNetAddress" => %{base: :ip_address, textual_convention: "IpAddress", display_hint: nil},
    "ipNetToMediaType" => %{base: :integer, textual_convention: nil, display_hint: nil},

    # BRIDGE-MIB (dot1dTpFdbTable and base)
    "dot1dBaseBridgeAddress" => %{base: :octet_string, textual_convention: "MacAddress", display_hint: nil},
    "dot1dBaseNumPorts" => %{base: :integer, textual_convention: nil, display_hint: nil},
    "dot1dBasePortIfIndex" => %{base: :integer, textual_convention: nil, display_hint: nil},
    "dot1dTpFdbAddress" => %{base: :octet_string, textual_convention: "MacAddress", display_hint: nil},
    "dot1dTpFdbPort" => %{base: :integer, textual_convention: nil, display_hint: nil},
    "dot1dTpFdbStatus" => %{base: :integer, textual_convention: nil, display_hint: nil},

    # IP-MIB (RFC 4293) modern ARP replacement: ipNetToPhysicalTable
    # Prefer these over ipNetToMedia*
    "ipNetToPhysicalIfIndex" => %{base: :integer, textual_convention: nil, display_hint: nil},
    "ipNetToPhysicalPhysAddress" => %{base: :octet_string, textual_convention: "PhysAddress", display_hint: nil},
    "ipNetToPhysicalNetAddress" => %{base: :octet_string, textual_convention: "InetAddress", display_hint: nil},
    "ipNetToPhysicalType" => %{base: :integer, textual_convention: nil, display_hint: nil},
    "ipNetToPhysicalLastUpdated" => %{base: :timeticks, textual_convention: "TimeStamp", display_hint: nil},

    # Q-BRIDGE-MIB (VLAN-aware FDB)
    "dot1qTpFdbAddress" => %{base: :octet_string, textual_convention: "MacAddress", display_hint: nil},
    "dot1qTpFdbPort" => %{base: :integer, textual_convention: nil, display_hint: nil},
    "dot1qTpFdbStatus" => %{base: :integer, textual_convention: nil, display_hint: nil}
  }

  ## Public API

  @doc """
  Starts the MIB registry GenServer.
  """
  def start_link(opts \\ []) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  @doc """
  Compiles a MIB file using SnmpKit.SnmpLib.MIB pure Elixir implementation.

  Enhanced to use SnmpKit.SnmpLib.MIB for improved compilation with better error handling.

  ## Examples

      iex> SnmpKit.SnmpMgr.MIB.compile("SNMPv2-MIB.mib")
      {:ok, "SNMPv2-MIB.bin"}

      iex> SnmpKit.SnmpMgr.MIB.compile("nonexistent.mib")
      {:error, :file_not_found}
  """
  def compile(mib_file, opts \\ []) do
    # Try SnmpKit.SnmpLib.MIB first for enhanced compilation
    case compile_with_snmp_lib(mib_file, opts) do
      {:ok, result} ->
        {:ok, result}

      {:error, :snmp_lib_not_available} ->
        {:error, :snmp_lib_not_available}

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

  @doc """
  Compiles all MIB files in a directory using enhanced SnmpKit.SnmpLib.MIB capabilities.
  """
  def compile_dir(directory, opts \\ []) do
    # Try SnmpKit.SnmpLib.MIB.compile_all first for enhanced batch compilation
    if File.exists?(directory) do
      case compile_all_with_snmp_lib(directory, opts) do
        {:ok, results} ->
          {:ok, results}

        {:error, :snmp_lib_not_available} ->
          # Fallback to individual file compilation
          compile_dir_fallback(directory, opts)

        {:error, reason} ->
          {:error, reason}
      end
    else
      {:error, {:directory_error, :enoent}}
    end
  end

  @doc """
  Returns enriched MIB metadata for an object by name or OID.

  Input may be a dotted OID string (with or without instance), an OID list,
  or a base name (optionally with an instance suffix like "ifDescr.6").

  Returns a map with at least: name (base symbol), base oid, and optional instance
  fields when input includes an instance. Includes curated syntax metadata for a
  subset of high-value IF-MIB objects as a stopgap until full compiler metadata
  is wired in.
  """
  @spec object_info(String.t() | [integer]) :: {:ok, map()} | {:error, term()}
  def object_info(name_or_oid) do
    with {:ok, input_oid} <- normalize_to_oid_list(name_or_oid),
         {:ok, base_name, _maybe_index} <- base_name_and_index(input_oid),
         {:ok, base_oid} <- name_to_oid(base_name) do
      # Prefer compiled/parsed metadata when available
      compiled_meta = GenServer.call(__MODULE__, {:get_metadata, base_name})

      syntax =
        case compiled_meta do
          %{syntax_base: base} = m ->
            %{base: base, textual_convention: Map.get(m, :textual_convention), display_hint: Map.get(m, :display_hint)}

          _ ->
            syntax_for(base_name)
        end

      base_map = %{
        name: base_name,
        module: module_for(base_name),
        oid: base_oid,
        syntax: syntax
      }

      enriched = maybe_put_instance(base_map, input_oid, base_oid)

      # Optionally add access/status/description if we have compiled metadata
      enriched =
        case compiled_meta do
          nil ->
            enriched

          m ->
            enriched
            |> maybe_put(:access, Map.get(m, :access))
            |> maybe_put(:status, Map.get(m, :status))
            |> maybe_put(:description, Map.get(m, :description))
        end

      {:ok, enriched}
    end
  end

  @doc """
  Alias for object_info/1 to match proposal naming.
  """
  @spec reverse_lookup_enriched(String.t() | [integer]) :: {:ok, map()} | {:error, term()}
  def reverse_lookup_enriched(name_or_oid), do: object_info(name_or_oid)

  @doc """
  Batch variant of object_info/1.
  Returns {:ok, list_of_maps} or {:error, reason} if any lookup fails.
  """
  @spec object_info_many([String.t() | [integer]]) :: {:ok, [map()]} | {:error, term()}
  def object_info_many(list) when is_list(list) do
    results = Enum.map(list, &object_info/1)

    case Enum.find(results, fn
           {:error, _} -> true
           _ -> false
         end) do
      {:error, reason} -> {:error, reason}
      _ -> {:ok, Enum.map(results, fn {:ok, m} -> m end)}
    end
  end

  @doc """
  Parses a MIB file to extract object definitions using SnmpKit.SnmpLib.MIB.Parser.

  This provides enhanced MIB analysis without requiring compilation.

  ## Examples

      iex> SnmpKit.SnmpMgr.MIB.parse_mib_file("SNMPv2-MIB.mib")
      {:ok, %{objects: [...], imports: [...], exports: [...]}}
  """
  def parse_mib_file(mib_file, opts \\ []) do
    case File.read(mib_file) do
      {:ok, content} ->
        parse_mib_content(content, opts)

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

  @doc """
  Parses MIB content string using SnmpKit.SnmpLib.MIB.Parser.

  ## Examples

      iex> content = "sysDescr OBJECT-TYPE SYNTAX DisplayString ACCESS read-only STATUS mandatory"
      iex> SnmpKit.SnmpMgr.MIB.parse_mib_content(content)
      {:ok, %{tokens: [...], parsed_objects: [...]}}
  """
  def parse_mib_content(content, opts \\ []) when is_binary(content) do
    # Use SnmpKit.SnmpLib.MIB.Parser for enhanced parsing
    case Parser.tokenize(content) do
      {:ok, tokens} ->
        {:ok, objects} = parse_tokens_to_objects(tokens, opts)

        {:ok,
         %{
           tokens: tokens,
           parsed_objects: objects,
           parser: :snmp_lib_enhanced
         }}

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

  @doc """
  Loads a compiled MIB file using SnmpKit.SnmpLib.MIB.load_compiled with fallback.
  """
  def load(compiled_mib_path) do
    # Try SnmpKit.SnmpLib.MIB.load_compiled first for enhanced loading
    case load_with_snmp_lib(compiled_mib_path) do
      {:ok, result} ->
        GenServer.call(__MODULE__, {:register_loaded_mib, result})

      {:error, :snmp_lib_not_available} ->
        GenServer.call(__MODULE__, {:load_mib, compiled_mib_path})

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

  @doc """
  Enhanced MIB object resolution with parsed MIB data integration.

  Returns enriched object information including OID, syntax, module, and more.
  Leverages both standard MIBs and any loaded/parsed MIB files for comprehensive resolution.

  ## Examples

      iex> SnmpKit.SnmpMgr.MIB.resolve_enhanced("sysDescr")
      {:ok, %{name: "sysDescr", oid: [1, 3, 6, 1, 2, 1, 1, 1], module: "SNMPv2-MIB", syntax: %{...}}}

      iex> SnmpKit.SnmpMgr.MIB.resolve_enhanced("sysDescr.0")
      {:ok, %{name: "sysDescr", oid: [1, 3, 6, 1, 2, 1, 1, 1], instance_oid: [1, 3, 6, 1, 2, 1, 1, 1, 0], ...}}
  """
  def resolve_enhanced(name, _opts \\ []) do
    # Use object_info for enriched resolution
    object_info(name)
  end

  @doc """
  Loads and parses a MIB file, integrating it into the name resolution system.

  This combines compilation/loading with parsing for comprehensive MIB support.
  """
  def load_and_integrate_mib(mib_file, opts \\ []) do
    with {:ok, compiled} <- compile(mib_file, opts),
         :ok <- GenServer.call(__MODULE__, {:integrate_mib_data, mib_file, compiled}) do
      {:ok, compiled}
    end
  end

  @doc """
  Loads standard MIBs that are built into the library.
  """
  def load_standard_mibs do
    GenServer.call(__MODULE__, :load_standard_mibs)
  end

  @doc """
  Resolves a symbolic name to an OID.

  ## Examples

      iex> SnmpKit.SnmpMgr.MIB.resolve("sysDescr.0")
      {:ok, [1, 3, 6, 1, 2, 1, 1, 1, 0]}

      iex> SnmpKit.SnmpMgr.MIB.resolve("sysDescr")
      {:ok, [1, 3, 6, 1, 2, 1, 1, 1]}

      iex> SnmpKit.SnmpMgr.MIB.resolve("unknownName")
      {:error, :not_found}
  """
  def resolve(name) do
    GenServer.call(__MODULE__, {:resolve, name})
  end

  @doc """
  Performs reverse lookup from OID to symbolic name.

  ## Examples

      iex> SnmpKit.SnmpMgr.MIB.reverse_lookup([1, 3, 6, 1, 2, 1, 1, 1, 0])
      {:ok, "sysDescr.0"}

      iex> SnmpKit.SnmpMgr.MIB.reverse_lookup([1, 3, 6, 1, 2, 1, 1, 1])
      {:ok, "sysDescr"}
  """
  def reverse_lookup(oid) when is_list(oid) do
    GenServer.call(__MODULE__, {:reverse_lookup, oid})
  end

  def reverse_lookup(oid_string) when is_binary(oid_string) do
    case OID.string_to_list(oid_string) do
      {:ok, oid_list} -> reverse_lookup(oid_list)
      error -> error
    end
  end

  @doc """
  Gets the children of an OID node.
  """
  def children(oid) do
    GenServer.call(__MODULE__, {:children, oid})
  end

  @doc """
  Gets the parent of an OID node.
  """
  def parent([_ | _] = oid) when is_list(oid) do
    {:ok, Enum.drop(oid, -1)}
  end

  def parent([]), do: {:error, :no_parent}

  def parent(oid_string) when is_binary(oid_string) do
    case OID.string_to_list(oid_string) do
      {:ok, oid_list} -> parent(oid_list)
      error -> error
    end
  end

  @doc """
  Walks the MIB tree starting from a root OID.
  """
  def walk_tree(root_oid, opts \\ []) do
    GenServer.call(__MODULE__, {:walk_tree, root_oid, opts})
  end

  ## GenServer Implementation

  @impl true
  def init(_opts) do
    # Initialize with standard MIBs
    reverse_map = build_reverse_map(@standard_mibs)

    state = %{
      name_to_oid: @standard_mibs,
      oid_to_name: reverse_map,
      name_to_meta: %{},
      loaded_mibs: [:standard]
    }

    {:ok, state}
  end

  @impl true
  def handle_call({:resolve, name}, _from, state) do
    result = resolve_name(name, state.name_to_oid)
    {:reply, result, state}
  end

  @impl true
  def handle_call({:reverse_lookup, oid}, _from, state) do
    result = reverse_lookup_oid(oid, state.oid_to_name)
    {:reply, result, state}
  end

  @impl true
  def handle_call({:children, oid}, _from, state) do
    result = find_children(oid, state.name_to_oid)
    {:reply, result, state}
  end

  @impl true
  def handle_call({:walk_tree, root_oid, _opts}, _from, state) do
    result = walk_tree_from_root(root_oid, state.name_to_oid)
    {:reply, result, state}
  end

  @impl true
  def handle_call(:load_standard_mibs, _from, state) do
    # Standard MIBs are already loaded in init
    {:reply, :ok, state}
  end

  @impl true
  def handle_call({:register_loaded_mib, mib_data}, _from, state) do
    # Register MIB data loaded via SnmpKit.SnmpLib.MIB.load_compiled
    new_state = merge_snmp_lib_mib_data(state, mib_data)
    {:reply, :ok, new_state}
  end

  @impl true
  def handle_call({:get_metadata, base_name}, _from, state) do
    meta = Map.get(state.name_to_meta, base_name)
    {:reply, meta, state}
  end

  @impl true
  def handle_call({:resolve_enhanced, name, _opts}, _from, state) do
    # Enhanced resolution using loaded MIB data
    result = resolve_with_loaded_mibs(name, state)
    {:reply, result, state}
  end

  @impl true
  def handle_call({:integrate_mib_data, mib_file, parsed_data}, _from, state) do
    # Integrate both compiled and parsed MIB data
    new_state = integrate_parsed_mib_data(state, mib_file, parsed_data)
    {:reply, :ok, new_state}
  end

  def handle_call({:bulk_register, mappings}, _from, state) do
    # Bulk register pre-compiled MIB mappings (from JSON files)
    merged_name_to_oid = Map.merge(state.name_to_oid, mappings)
    merged_oid_to_name = build_reverse_map(merged_name_to_oid)

    new_state =
      state
      |> Map.put(:name_to_oid, merged_name_to_oid)
      |> Map.put(:oid_to_name, merged_oid_to_name)

    {:reply, :ok, new_state}
  end

  ## Private Functions

  defp compile_with_snmp_lib(mib_file, opts) do
    case MIB.compile(mib_file, opts) do
      {:ok, result} -> {:ok, result}
      {:error, reason} -> {:error, {:snmp_lib_compilation_failed, reason}}
    end
  rescue
    UndefinedFunctionError -> {:error, :snmp_lib_not_available}
  end

  defp compile_all_with_snmp_lib(directory, opts) do
    case File.ls(directory) do
      {:ok, files} ->
        mib_files =
          files
          |> Enum.filter(&String.ends_with?(&1, ".mib"))
          |> Enum.map(&Path.join(directory, &1))

        case MIB.compile_all(mib_files, opts) do
          {:ok, results} -> {:ok, results}
          {:error, reason} -> {:error, {:snmp_lib_batch_compilation_failed, reason}}
        end

      {:error, reason} ->
        {:error, {:directory_error, reason}}
    end
  rescue
    UndefinedFunctionError -> {:error, :snmp_lib_not_available}
  end

  defp extract_name_to_oid_from_symbols(symbols) when is_map(symbols) do
    Enum.reduce(symbols, %{}, fn {name, defn}, acc ->
      extract_single_name_to_oid(name, defn, acc)
    end)
  end

  defp extract_single_name_to_oid(name, %{} = defn, acc) do
    case Map.get(defn, :oid) do
      nil -> acc
      oid_any -> maybe_add_normalized_oid(name, oid_any, acc)
    end
  end

  defp extract_single_name_to_oid(_name, _defn, acc), do: acc

  defp maybe_add_normalized_oid(name, oid_any, acc) do
    case normalize_parsed_oid(oid_any) do
      {:ok, oid_list} -> Map.put(acc, name, oid_list)
      _ -> acc
    end
  end

  defp extract_meta_from_symbols(symbols) when is_map(symbols) do
    Enum.reduce(symbols, %{}, fn {name, defn}, acc ->
      extract_single_meta(name, defn, acc)
    end)
  end

  defp extract_single_meta(name, %{__type__: :object_type} = defn, acc) do
    meta = build_meta_from_object_type(defn)
    Map.put(acc, name, meta)
  end

  defp extract_single_meta(_name, _defn, acc), do: acc

  defp build_meta_from_object_type(defn) do
    syntax_any = Map.get(defn, :syntax)

    %{
      syntax_base: syntax_base_from(syntax_any),
      textual_convention: textual_convention_from(syntax_any),
      display_hint: nil,
      access: Map.get(defn, :max_access),
      status: Map.get(defn, :status),
      description: Map.get(defn, :description)
    }
  end

  defp compile_dir_fallback(directory, opts) do
    case File.ls(directory) do
      {:ok, files} ->
        mib_files = Enum.filter(files, &String.ends_with?(&1, ".mib"))

        results =
          Enum.map(mib_files, fn file ->
            file_path = Path.join(directory, file)
            {file, compile(file_path, opts)}
          end)

        {:ok, results}

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

  defp parse_tokens_to_objects(tokens, _opts) do
    # Extract OBJECT-TYPE definitions from tokens
    objects = extract_object_definitions(tokens)
    {:ok, objects}
  end

  defp extract_object_definitions(tokens) do
    # Simple object extraction - can be enhanced further
    tokens
    |> Enum.chunk_every(3, 1, :discard)
    |> Enum.filter(fn
      [{:atom, _, name}, {:"OBJECT-TYPE", _}, _] ->
        %{name: name, type: :object}

      _ ->
        false
    end)
    |> Enum.map(fn [{:atom, _, name}, {:"OBJECT-TYPE", _}, _] ->
      %{name: name, type: :object_type}
    end)
  end

  defp load_with_snmp_lib(compiled_mib_path) do
    case MIB.load_compiled(compiled_mib_path) do
      {:ok, result} -> {:ok, result}
      {:error, reason} -> {:error, {:snmp_lib_load_failed, reason}}
    end
  rescue
    UndefinedFunctionError -> {:error, :snmp_lib_not_available}
  end

  # Derive base syntax from parsed syntax term
  defp syntax_base_from(syntax) do
    case syntax do
      atom when is_atom(atom) ->
        syntax_atom_to_base(atom)

      {type, _} when type in [:integer, :octet_string, :object_identifier] ->
        type

      {:type, t} when is_atom(t) ->
        syntax_type_to_base(t)

      _ ->
        nil
    end
  end

  # Map simple syntax atoms to base types
  defp syntax_atom_to_base(atom) do
    base_types = [
      :integer,
      :octet_string,
      :object_identifier,
      :timeticks,
      :counter32,
      :counter64,
      :gauge32,
      :ip_address
    ]

    if atom in base_types, do: atom
  end

  # Handle typed syntax conversions
  defp syntax_type_to_base(type) do
    case type do
      :"octet string" -> :octet_string
      :"object identifier" -> :object_identifier
      other -> other
    end
  end

  # Best-effort textual convention detection from syntax term
  defp textual_convention_from(syntax) do
    case syntax do
      {:type, t} when is_atom(t) -> Atom.to_string(t)
      _ -> nil
    end
  end

  defp merge_snmp_lib_mib_data(state, mib_data) do
    # Accept either compiled format with :symbols or a parsed map with name_to_oid/name_to_meta
    {add_map, add_meta} =
      cond do
        is_map(mib_data) and Map.has_key?(mib_data, :symbols) ->
          symbols = Map.get(mib_data, :symbols, %{})
          {extract_name_to_oid_from_symbols(symbols), extract_meta_from_symbols(symbols)}

        is_map(mib_data) and Map.has_key?(mib_data, :name_to_oid) ->
          raw = Map.get(mib_data, :name_to_oid, %{})
          meta = Map.get(mib_data, :name_to_meta, %{})
          {normalize_name_to_oid(raw), meta}

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

    merged_name_to_oid = Map.merge(state.name_to_oid, add_map)
    merged_oid_to_name = build_reverse_map(merged_name_to_oid)
    merged_name_to_meta = Map.merge(state.name_to_meta, add_meta)

    state
    |> Map.put(:name_to_oid, merged_name_to_oid)
    |> Map.put(:oid_to_name, merged_oid_to_name)
    |> Map.put(:name_to_meta, merged_name_to_meta)
    |> Map.update(:snmp_lib_mibs, [mib_data], fn list -> [mib_data | list] end)
  end

  defp resolve_with_loaded_mibs(name, state) do
    case Map.get(state, :name_to_oid) do
      %{} = m when is_binary(name) ->
        case Map.get(m, name) do
          nil -> {:error, :not_found}
          oid -> {:ok, oid}
        end

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

  defp integrate_parsed_mib_data(state, mib_file, compiled_data) do
    # Read the MIB file content for workaround
    mib_content =
      case File.read(mib_file) do
        {:ok, content} -> content
        _ -> ""
      end

    # Extract OID mappings from compiled MIB symbols
    mib_mappings = extract_mib_mappings_from_compiled(compiled_data, state, mib_content)

    # Merge extracted OIDs into state
    merged_name_to_oid = Map.merge(state.name_to_oid, mib_mappings.name_to_oid)
    merged_oid_to_name = build_reverse_map(merged_name_to_oid)
    merged_name_to_meta = Map.merge(state.name_to_meta, mib_mappings.name_to_meta)

    # Also store raw compiled data
    integrated_mibs = Map.get(state, :integrated_mibs, %{})
    new_integrated = Map.put(integrated_mibs, mib_file, compiled_data)

    state
    |> Map.put(:name_to_oid, merged_name_to_oid)
    |> Map.put(:oid_to_name, merged_oid_to_name)
    |> Map.put(:name_to_meta, merged_name_to_meta)
    |> Map.put(:integrated_mibs, new_integrated)
  end

  # Extract OID mappings from compiled MIB data (symbols format)
  defp extract_mib_mappings_from_compiled(compiled_data, state, mib_content) do
    symbols = Map.get(compiled_data, :symbols, %{})

    # Convert symbols to definitions format
    definitions =
      Enum.map(symbols, fn {name, symbol_data} ->
        Map.put(symbol_data, :name, name)
      end)

    # Reuse existing extraction logic
    extract_mib_mappings(%{definitions: definitions}, state, mib_content)
  end

  defp resolve_name(name, _name_to_oid_map) when is_nil(name) or not is_binary(name) do
    {:error, :invalid_name}
  end

  defp resolve_name(name, name_to_oid_map) do
    case Map.get(name_to_oid_map, name) do
      nil -> resolve_name_with_instance(name, name_to_oid_map)
      oid -> {:ok, oid}
    end
  end

  defp resolve_name_with_instance(name, name_to_oid_map) do
    if String.contains?(name, ".") do
      parse_name_with_instance(name, name_to_oid_map)
    else
      {:error, :not_found}
    end
  end

  defp parse_name_with_instance(name, name_to_oid_map) do
    [base_name | instance_parts] = String.split(name, ".")

    with {:ok, base_oid} <- get_base_oid(base_name, name_to_oid_map),
         {:ok, instance_oids} <- parse_instance_parts(instance_parts) do
      {:ok, base_oid ++ instance_oids}
    end
  end

  defp get_base_oid(base_name, name_to_oid_map) do
    case Map.get(name_to_oid_map, base_name) do
      nil -> {:error, :not_found}
      oid -> {:ok, oid}
    end
  end

  defp parse_instance_parts(parts) do
    parts
    |> Enum.reduce_while([], fn part, acc ->
      case Integer.parse(part) do
        {int, ""} -> {:cont, [int | acc]}
        _ -> {:halt, :error}
      end
    end)
    |> case do
      :error -> {:error, :invalid_instance}
      oids -> {:ok, Enum.reverse(oids)}
    end
  end

  defp reverse_lookup_oid(oid, oid_to_name_map) do
    case Map.get(oid_to_name_map, oid) do
      nil ->
        # Try to find a partial match
        find_partial_reverse_match(oid, oid_to_name_map)

      name ->
        # Exact match - return as-is (already includes any suffix in the map)
        {:ok, name}
    end
  end

  defp find_partial_reverse_match(oid, oid_to_name_map) do
    # Handle case where oid might be a string instead of list
    if is_binary(oid) do
      {:error, :invalid_oid_format}
    else
      # Handle empty list case
      if Enum.empty?(oid) do
        {:error, :empty_oid}
      else
        # Try progressively shorter OIDs to find a base match
        find_partial_match(oid, oid_to_name_map, length(oid) - 1)
      end
    end
  end

  defp find_partial_match(_oid, _map, length) when length <= 0, do: {:error, :not_found}

  defp find_partial_match(oid, oid_to_name_map, length) do
    partial_oid = Enum.take(oid, length)

    case Map.get(oid_to_name_map, partial_oid) do
      nil ->
        find_partial_match(oid, oid_to_name_map, length - 1)

      base_name ->
        # Found a base match - append the remaining OID elements as the index suffix
        base = strip_instance_suffix(base_name)
        suffix = Enum.drop(oid, length)

        case suffix do
          [] -> {:ok, base}
          _ -> {:ok, base <> "." <> Enum.join(suffix, ".")}
        end
    end
  end

  defp find_children(parent_oid, name_to_oid_map) do
    case normalize_parent_oid_for_children(parent_oid) do
      {:ok, normalized_oid} ->
        children = filter_and_collect_children(normalized_oid, name_to_oid_map)
        {:ok, children}

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

  # Normalize parent OID to list format
  defp normalize_parent_oid_for_children(nil), do: {:ok, []}
  defp normalize_parent_oid_for_children(parent_oid) when is_list(parent_oid), do: {:ok, parent_oid}

  defp normalize_parent_oid_for_children(parent_oid) when is_binary(parent_oid) do
    case OID.string_to_list(parent_oid) do
      {:ok, oid_list} -> {:ok, oid_list}
      {:error, _} -> {:error, :invalid_parent_oid}
    end
  end

  defp normalize_parent_oid_for_children(_), do: {:error, :invalid_parent_oid}

  # Filter and collect direct children of parent OID
  defp filter_and_collect_children(normalized_oid, name_to_oid_map) do
    name_to_oid_map
    |> Enum.filter(fn {_name, oid} ->
      direct_child_of?(oid, normalized_oid)
    end)
    |> Enum.map(fn {name, _oid} -> name end)
    |> Enum.sort()
  end

  # Check if oid is a direct child of parent_oid
  defp direct_child_of?(oid, parent_oid) when is_list(oid) and is_list(parent_oid) do
    length(oid) == length(parent_oid) + 1 and List.starts_with?(oid, parent_oid)
  end

  defp direct_child_of?(_oid, _parent_oid), do: false

  defp walk_tree_from_root(root_oid, name_to_oid_map) do
    root_oid =
      cond do
        is_binary(root_oid) ->
          case OID.string_to_list(root_oid) do
            {:ok, oid_list} -> oid_list
            {:error, _} -> []
          end

        is_list(root_oid) ->
          root_oid

        is_nil(root_oid) ->
          []

        true ->
          []
      end

    descendants =
      name_to_oid_map
      |> Enum.filter(fn {_name, oid} ->
        is_list(oid) and List.starts_with?(oid, root_oid)
      end)
      |> Enum.map(fn {name, oid} -> {name, oid} end)
      |> Enum.sort_by(fn {_name, oid} -> oid end)

    {:ok, descendants}
  end

  defp build_reverse_map(name_to_oid_map) do
    Map.new(name_to_oid_map, fn {name, oid} -> {oid, name} end)
  end

  # Normalize any dotted instance suffix from a name like "ifDescr.1" -> "ifDescr"
  defp strip_instance_suffix(name) when is_binary(name) do
    case String.split(name, ".", parts: 2) do
      [base] -> base
      [base, _rest] -> base
    end
  end

  defp strip_instance_suffix(other), do: other

  defp normalize_to_oid_list(oid_any) when is_list(oid_any) do
    case OID.valid_oid?(oid_any) do
      :ok -> {:ok, oid_any}
      error -> error
    end
  end

  defp normalize_to_oid_list(oid_any) when is_binary(oid_any) do
    if numeric_oid_string?(oid_any) do
      OID.string_to_list(oid_any)
    else
      resolve_named_oid(oid_any)
    end
  end

  defp normalize_to_oid_list(_), do: {:error, :invalid_input}

  defp numeric_oid_string?(str) do
    String.contains?(str, ".") and String.match?(str, ~r/^\.?\d+(?:\.\d+)*$/)
  end

  defp resolve_named_oid(oid_str) do
    case String.split(oid_str, ".", parts: 2) do
      [base] -> resolve(base)
      [base, instance_str] -> resolve_with_instance(base, instance_str)
    end
  end

  defp resolve_with_instance(base, instance_str) do
    with {:ok, base_oid} <- resolve(base),
         {:ok, instance_index} <- parse_instance(instance_str) do
      {:ok, base_oid ++ instance_index}
    end
  end

  defp parse_instance(instance_str) do
    parts = String.split(instance_str, ".")

    try do
      ints =
        Enum.map(parts, fn p ->
          case Integer.parse(p) do
            {i, ""} -> i
            _ -> throw(:bad)
          end
        end)

      {:ok, ints}
    catch
      :bad -> {:error, :invalid_instance}
    end
  end

  defp base_name_and_index(oid_list) do
    with {:ok, name_with_index} <- reverse_lookup(oid_list),
         base_name = strip_instance_suffix(name_with_index),
         {:ok, base_oid} <- name_to_oid(base_name) do
      extract_index_from_oid(oid_list, base_name, base_oid)
    end
  end

  defp extract_index_from_oid(oid_list, base_name, base_oid) do
    base_len = length(base_oid)

    if length(oid_list) > base_len do
      {:ok, base_name, Enum.drop(oid_list, base_len)}
    else
      {:ok, base_name, nil}
    end
  end

  defp name_to_oid(name) when is_binary(name) do
    case resolve(name) do
      {:ok, oid} -> {:ok, oid}
      error -> error
    end
  end

  defp maybe_put_instance(map, input_oid, base_oid) do
    base_len = length(base_oid)

    if length(input_oid) > base_len do
      instance = Enum.drop(input_oid, base_len)

      instance_index =
        case instance do
          [i] -> i
          list -> list
        end

      map
      |> Map.put(:instance_index, instance_index)
      |> Map.put(:instance_oid, input_oid)
    else
      map
    end
  end

  defp syntax_for(base_name) do
    Map.get(@curated_syntax, base_name, %{base: nil, textual_convention: nil, display_hint: nil})
  end

  defp maybe_put(map, _key, nil), do: map
  defp maybe_put(map, key, val), do: Map.put(map, key, val)

  defp module_for(base_name) do
    # Map of prefix patterns to MIB modules
    # Order matters: more specific prefixes before general ones
    prefix_to_module = [
      {"ifHC", "IF-MIB"},
      {"ifIn", "IF-MIB"},
      {"ifOut", "IF-MIB"},
      {"if", "IF-MIB"},
      {"ipNetToPhysical", "IP-MIB"},
      {"ipNetToMedia", "IP-MIB"},
      {"sys", "SNMPv2-MIB"},
      {"dot1d", "BRIDGE-MIB"},
      {"dot1q", "Q-BRIDGE-MIB"}
    ]

    Enum.find_value(prefix_to_module, fn {prefix, module} ->
      if String.starts_with?(base_name, prefix), do: module
    end)
  end

  # Normalize name->oid map from arbitrary representations
  defp normalize_name_to_oid(raw) when is_map(raw) do
    Enum.reduce(raw, %{}, fn {name, oid_any}, acc ->
      case normalize_parsed_oid(oid_any) do
        {:ok, oid_list} -> Map.put(acc, name, oid_list)
        _ -> acc
      end
    end)
  end

  # Convert parsed OID representation to a flat integer list when possible
  defp normalize_parsed_oid(oid) when is_list(oid) do
    if Enum.all?(oid, &is_integer/1) do
      {:ok, oid}
    else
      parse_complex_oid_elements(oid)
    end
  end

  # Handle tuple format: {parent_name, index_binary}
  # This format is used by the MIB parser for OID references
  defp normalize_parsed_oid({parent_name, index_binary}) when is_atom(parent_name) and is_binary(index_binary) do
    # Try to resolve the parent OID from current state
    parent_str = Atom.to_string(parent_name)

    case resolve(parent_str) do
      {:ok, parent_oid} when is_list(parent_oid) ->
        # Parse the index as an integer
        case :binary.decode_unsigned(index_binary) do
          index when is_integer(index) ->
            {:ok, parent_oid ++ [index]}

          _ ->
            {:error, :invalid_index}
        end

      _ ->
        # Parent not resolved yet - will be handled in second pass
        {:error, :unresolved_parent}
    end
  end

  defp normalize_parsed_oid(_), do: {:error, :invalid_oid}

  # Handle lists like [%{value: 1}, %{value: 3}, ...] possibly with names
  defp parse_complex_oid_elements(oid) do
    vals = Enum.map(oid, &normalize_oid_element/1)

    if Enum.any?(vals, &(&1 == :error)) do
      {:error, :unresolved_oid}
    else
      {:ok, Enum.map(vals, fn {:ok, i} -> i end)}
    end
  end

  # Normalize a single OID element to an integer
  defp normalize_oid_element(%{value: v}) when is_integer(v), do: {:ok, v}

  defp normalize_oid_element(%{value: v}) when is_binary(v) do
    case Integer.parse(v) do
      {i, ""} -> {:ok, i}
      _ -> :error
    end
  end

  defp normalize_oid_element(v) when is_integer(v), do: {:ok, v}
  defp normalize_oid_element(_), do: :error

  defp extract_mib_mappings(mib_data, state, mib_content) do
    # Extract name-to-OID mappings and basic metadata from parsed MIB data
    definitions = Map.get(mib_data, :definitions, [])
    tc_map = build_tc_map(definitions)

    # Build a map of name to OID definition for resolution
    oid_defs = build_oid_definition_map(definitions)

    # Workaround for parser bug: manually extract definitions with missing sub_index
    oid_defs = fix_missing_sub_indices(oid_defs, mib_content)

    # Resolve OIDs recursively, using existing state for parent resolution
    name_to_oid_map = resolve_all_oids(oid_defs, state.name_to_oid)

    # Build metadata for object types
    name_to_meta = build_metadata_map(definitions, tc_map)

    %{name_to_oid: name_to_oid_map, name_to_meta: name_to_meta}
  end

  # Workaround for parser bug: extract OBJECT-IDENTIFIER definitions with missing sub_index
  # The parser fails to extract sub_index when the value is 10 (newline character)
  defp fix_missing_sub_indices(oid_defs, mib_content) do
    # Find entries with nil sub_index but parent reference
    missing_indices =
      oid_defs
      |> Enum.filter(fn {_name, oid_def} ->
        case oid_def do
          {_parent, nil} -> true
          _ -> false
        end
      end)
      |> Enum.map(fn {name, _} -> name end)

    # For each missing entry, try to extract from raw MIB content
    Enum.reduce(missing_indices, oid_defs, fn name, acc ->
      # Pattern: name OBJECT IDENTIFIER ::= { parent index }
      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)
          # Convert to the same format as other indices (binary with the integer as byte)
          parent_atom = String.to_atom(parent)
          Map.put(acc, name, {parent_atom, <<index>>})

        _ ->
          acc
      end
    end)
  end

  # Build textual convention map from definitions
  defp build_tc_map(definitions) do
    definitions
    |> Enum.filter(&(Map.get(&1, :__type__) == :textual_convention))
    |> Enum.reduce(%{}, fn tc, acc ->
      tc_name = Map.get(tc, :name)
      tc_syntax = Map.get(tc, :syntax)
      display_hint = Map.get(tc, :display_hint)
      Map.put(acc, tc_name, %{syntax_base: syntax_base_from(tc_syntax), display_hint: display_hint})
    end)
  end

  # Build a map of name to OID definition for resolution
  defp build_oid_definition_map(definitions) do
    definitions
    |> Enum.filter(fn def ->
      type = Map.get(def, :__type__)
      type in [:object_type, :object_identifier, :module_identity]
    end)
    |> 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
        # Object has explicit OID (e.g., {:parentName, <<index>>})
        name && oid ->
          Map.put(acc, name, oid)

        # OBJECT-IDENTIFIER with parent and sub_index
        name && parent && sub_index ->
          # Convert parent/sub_index to the same format as OID tuples
          parent_atom = String.to_atom(parent)
          Map.put(acc, name, {parent_atom, sub_index})

        # OBJECT-IDENTIFIER with parent but missing sub_index (parser bug)
        # Mark with nil so fix_missing_sub_indices can handle it
        name && parent ->
          parent_atom = String.to_atom(parent)
          Map.put(acc, name, {parent_atom, nil})

        # No usable data
        true ->
          acc
      end
    end)
  end

  # Resolve all OIDs recursively
  defp resolve_all_oids(oid_defs, existing_oids) do
    # Start with existing OIDs from state and iterate until all resolvable OIDs are resolved
    resolve_oids_iteratively(oid_defs, existing_oids, 10)
  end

  defp resolve_oids_iteratively(_oid_defs, resolved, 0) do
    # Max iterations reached, return what we have
    resolved
  end

  defp resolve_oids_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(oid_def, res_acc) do
          {:ok, oid_list} ->
            {Map.put(res_acc, name, oid_list), rem_acc}

          {:error, _reason} ->
            {res_acc, Map.put(rem_acc, name, oid_def)}
        end
      end)

    if map_size(remaining) == map_size(oid_defs) do
      # No progress made, stop iterating
      new_resolved
    else
      # Continue with remaining unresolved OIDs
      resolve_oids_iteratively(remaining, new_resolved, iterations_left - 1)
    end
  end

  defp resolve_single_oid(oid_def, _resolved) when is_list(oid_def) do
    # Already a list of integers
    case oid_def do
      [] ->
        {:error, :empty_oid}

      _ ->
        if Enum.all?(oid_def, &is_integer/1) do
          {:ok, oid_def}
        else
          {:error, :not_resolved}
        end
    end
  end

  defp resolve_single_oid({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) ->
        # Decode the index - MIB parser uses UTF-8 character for numeric values
        index = decode_mib_index(index_binary)
        {:ok, parent_oid ++ [index]}

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

  defp resolve_single_oid(_oid_def, _resolved), do: {:error, :invalid_format}

  # Decode MIB index from binary
  # The parser represents integers as UTF-8 characters where the code point is the value
  defp decode_mib_index(binary) when is_binary(binary) do
    case String.to_charlist(binary) do
      [code_point | _] -> code_point
      [] -> :binary.decode_unsigned(binary)
    end
  end

  # Build metadata map for object types
  defp build_metadata_map(definitions, tc_map) do
    definitions
    |> Enum.filter(&(Map.get(&1, :__type__) == :object_type))
    |> Enum.reduce(%{}, fn def, acc ->
      name = Map.get(def, :name)

      if name do
        meta = build_object_type_meta(def, tc_map)
        Map.put(acc, name, meta)
      else
        acc
      end
    end)
  end

  # Build metadata for an object type definition
  defp build_object_type_meta(defn, tc_map) do
    syntax_any = Map.get(defn, :syntax)
    {syntax_base, textual_convention, display_hint} = resolve_syntax_metadata(syntax_any, tc_map)

    %{
      syntax_base: syntax_base,
      textual_convention: textual_convention,
      display_hint: display_hint,
      access: Map.get(defn, :max_access),
      status: Map.get(defn, :status),
      description: Map.get(defn, :description)
    }
  end

  # Resolve syntax metadata, looking up textual conventions
  defp resolve_syntax_metadata(syntax_any, tc_map) when is_atom(syntax_any) do
    primitives =
      MapSet.new([
        :integer,
        :octet_string,
        :object_identifier,
        :timeticks,
        :counter32,
        :counter64,
        :gauge32,
        :ip_address
      ])

    if MapSet.member?(primitives, syntax_any) do
      {syntax_base_from(syntax_any), textual_convention_from(syntax_any), nil}
    else
      lookup_tc_metadata(syntax_any, tc_map)
    end
  end

  defp resolve_syntax_metadata(syntax_any, _tc_map) do
    {syntax_base_from(syntax_any), textual_convention_from(syntax_any), nil}
  end

  # Look up textual convention metadata
  defp lookup_tc_metadata(syntax_atom, tc_map) do
    tc_key = Atom.to_string(syntax_atom)

    case Map.get(tc_map, tc_key) do
      %{syntax_base: base, display_hint: hint} ->
        {base, tc_key, hint}

      _ ->
        {syntax_base_from(syntax_atom), textual_convention_from(syntax_atom), nil}
    end
  end
end
