defmodule Towerops.Snmp.MibValidationTest do
  @moduledoc """
  Tests to validate that hardcoded OIDs in profile modules match the official MIB definitions.

  This ensures our OID constants stay in sync with the authoritative MIB files.
  """

  use ExUnit.Case, async: true

  alias Towerops.Snmp.MibParser

  @mibs_dir Application.app_dir(:towerops, "priv/mibs")

  describe "Base profile OID validation" do
    test "system OIDs match SNMPv2-MIB" do
      mib_file = Path.join([@mibs_dir, "standard", "SNMPv2-MIB"])

      # These are the OIDs we use in Base.discover_system_info
      validations = [
        {"sysDescr", "1.3.6.1.2.1.1.1.0"},
        {"sysObjectID", "1.3.6.1.2.1.1.2.0"},
        {"sysUpTime", "1.3.6.1.2.1.1.3.0"},
        {"sysContact", "1.3.6.1.2.1.1.4.0"},
        {"sysName", "1.3.6.1.2.1.1.5.0"},
        {"sysLocation", "1.3.6.1.2.1.1.6.0"}
      ]

      case MibParser.parse_mib_file(mib_file) do
        {:ok, oids} ->
          Enum.each(validations, fn {object_name, expected_oid} ->
            # MIB defines the base OID, we add .0 for instance
            base_expected = String.replace_suffix(expected_oid, ".0", "")

            case Map.get(oids, object_name) do
              ^base_expected ->
                :ok

              nil ->
                # Object might not be in this particular MIB file, skip
                :ok

              actual_oid ->
                flunk("OID mismatch for #{object_name}: expected #{base_expected}, got #{actual_oid}")
            end
          end)

        {:error, _reason} ->
          # MIB parsing failed, skip this test (MIB file might not exist yet)
          :ok
      end
    end

    test "interface OIDs match IF-MIB" do
      mib_file = Path.join([@mibs_dir, "standard", "IF-MIB"])

      validations = [
        {"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"},
        {"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"},
        {"ifName", "1.3.6.1.2.1.31.1.1.1.1"},
        {"ifAlias", "1.3.6.1.2.1.31.1.1.1.18"}
      ]

      validate_oids(mib_file, validations)
    end

    test "sensor OIDs match ENTITY-SENSOR-MIB" do
      mib_file = Path.join([@mibs_dir, "standard", "ENTITY-SENSOR-MIB"])

      validations = [
        {"entPhySensorType", "1.3.6.1.2.1.99.1.1.1.1"},
        {"entPhySensorScale", "1.3.6.1.2.1.99.1.1.1.2"},
        {"entPhySensorValue", "1.3.6.1.2.1.99.1.1.1.4"},
        {"entPhySensorOperStatus", "1.3.6.1.2.1.99.1.1.1.5"}
      ]

      validate_oids(mib_file, validations)
    end
  end

  describe "Cisco profile OID validation" do
    test "Cisco sensor OIDs match CISCO-ENTITY-SENSOR-MIB" do
      mib_file = Path.join([@mibs_dir, "cisco", "CISCO-ENTITY-SENSOR-MIB"])

      validations = [
        {"entSensorType", "1.3.6.1.4.1.9.9.91.1.1.1.1.1"},
        {"entSensorScale", "1.3.6.1.4.1.9.9.91.1.1.1.1.2"},
        {"entSensorValue", "1.3.6.1.4.1.9.9.91.1.1.1.1.4"},
        {"entSensorStatus", "1.3.6.1.4.1.9.9.91.1.1.1.1.5"}
      ]

      validate_oids(mib_file, validations)
    end
  end

  describe "NetSnmp profile OID validation" do
    test "LM-SENSORS OIDs match LM-SENSORS-MIB" do
      mib_file = Path.join([@mibs_dir, "net-snmp", "LM-SENSORS-MIB"])

      validations = [
        {"lmTempSensorsDevice", "1.3.6.1.4.1.2021.13.16.2.1.2"},
        {"lmTempSensorsValue", "1.3.6.1.4.1.2021.13.16.2.1.3"},
        {"lmFanSensorsDevice", "1.3.6.1.4.1.2021.13.16.3.1.2"},
        {"lmFanSensorsValue", "1.3.6.1.4.1.2021.13.16.3.1.3"},
        {"lmVoltSensorsDevice", "1.3.6.1.4.1.2021.13.16.4.1.2"},
        {"lmVoltSensorsValue", "1.3.6.1.4.1.2021.13.16.4.1.3"}
      ]

      validate_oids(mib_file, validations)
    end

    test "UCD-SNMP OIDs match UCD-SNMP-MIB" do
      mib_file = Path.join([@mibs_dir, "net-snmp", "UCD-SNMP-MIB"])

      validations = [
        {"laLoad1", "1.3.6.1.4.1.2021.10.1.3.1"},
        {"laLoad5", "1.3.6.1.4.1.2021.10.1.3.2"},
        {"laLoad15", "1.3.6.1.4.1.2021.10.1.3.3"},
        {"memTotalReal", "1.3.6.1.4.1.2021.4.5.0"},
        {"memAvailReal", "1.3.6.1.4.1.2021.4.6.0"}
      ]

      # Remove .0 suffix for table entries
      validations =
        Enum.map(validations, fn {name, oid} ->
          {name, String.replace_suffix(oid, ".0", "")}
        end)

      validate_oids(mib_file, validations)
    end
  end

  describe "MIB parser functionality" do
    test "can parse standard MIB files" do
      mib_files = [
        Path.join([@mibs_dir, "standard", "SNMPv2-MIB"]),
        Path.join([@mibs_dir, "standard", "IF-MIB"]),
        Path.join([@mibs_dir, "standard", "ENTITY-SENSOR-MIB"])
      ]

      Enum.each(mib_files, fn mib_file ->
        if File.exists?(mib_file) do
          case MibParser.parse_mib_file(mib_file) do
            {:ok, oids} ->
              assert is_map(oids)
              assert map_size(oids) > 0

            {:error, reason} ->
              flunk("Failed to parse #{Path.basename(mib_file)}: #{inspect(reason)}")
          end
        end
      end)
    end

    test "list_mib_files returns all MIB files" do
      mib_files = MibParser.list_mib_files()

      assert is_list(mib_files)

      # Should have at least the standard MIBs
      assert Enum.any?(mib_files, &String.contains?(&1, "IF-MIB"))
      assert Enum.any?(mib_files, &String.contains?(&1, "SNMPv2-MIB"))
    end
  end

  # Helper function to validate a list of OIDs against a MIB file
  defp validate_oids(mib_file, validations) do
    case MibParser.parse_mib_file(mib_file) do
      {:ok, oids} ->
        Enum.each(validations, &validate_single_oid(mib_file, oids, &1))

      {:error, :enoent} ->
        # MIB file doesn't exist yet, skip validation
        :ok

      {:error, reason} ->
        flunk("Failed to parse #{Path.basename(mib_file)}: #{inspect(reason)}")
    end
  end

  defp validate_single_oid(mib_file, oids, {object_name, expected_oid}) do
    case Map.get(oids, object_name) do
      ^expected_oid ->
        :ok

      nil ->
        # Object might not be in this particular MIB file
        # This is OK - some objects might be defined elsewhere
        :ok

      actual_oid ->
        flunk(
          "OID mismatch in #{Path.basename(mib_file)} for #{object_name}: expected #{expected_oid}, got #{actual_oid}"
        )
    end
  end
end
