defmodule Towerops.Snmp.Profiles.NetSnmpTest do
  use ExUnit.Case, async: true

  import Mox

  alias Towerops.Snmp.Profiles.NetSnmp
  alias Towerops.Snmp.SnmpMock

  setup :verify_on_exit!

  @client_opts [ip: "192.168.1.100", community: "public", version: :v2c, port: 161]

  describe "discover_system_info/1" do
    test "delegates to Base profile" do
      # Mock the SNMP calls that Base.discover_system_info makes
      # Base profile queries 6 system OIDs - match by OID string
      expect(SnmpMock, :get, 6, fn _target, oid, _opts ->
        case oid do
          "1.3.6.1.2.1.1.1.0" -> {:ok, "Linux ubuntu 5.4.0-42-generic #46-Ubuntu"}
          "1.3.6.1.2.1.1.2.0" -> {:ok, [1, 3, 6, 1, 4, 1, 8072]}
          "1.3.6.1.2.1.1.3.0" -> {:ok, 123_456}
          "1.3.6.1.2.1.1.4.0" -> {:ok, "root@localhost"}
          "1.3.6.1.2.1.1.5.0" -> {:ok, "ubuntu-server"}
          "1.3.6.1.2.1.1.6.0" -> {:ok, "Data Center"}
          _ -> {:error, :unknown_oid}
        end
      end)

      result = NetSnmp.discover_system_info(@client_opts)

      assert {:ok, system_info} = result
      assert is_map(system_info)
      assert system_info.sys_name == "ubuntu-server"
    end
  end

  describe "discover_interfaces/1" do
    test "delegates to Base profile" do
      # Mock SNMP walk for interfaces
      expect(SnmpMock, :walk, fn _target, _oid, _opts ->
        {:ok, []}
      end)

      result = NetSnmp.discover_interfaces(@client_opts)

      assert {:ok, []} = result
    end
  end

  describe "discover_sensors/1" do
    test "discovers LM-SENSORS temperature sensors" do
      # Mock walks for LM-SENSORS temperature devices and values
      expect(SnmpMock, :walk, 6, fn _target, oid, _opts ->
        case oid do
          "1.3.6.1.4.1.2021.13.16.2.1.2" ->
            {:ok,
             [
               %{oid: "1.3.6.1.4.1.2021.13.16.2.1.2.1", value: "Core 0"},
               %{oid: "1.3.6.1.4.1.2021.13.16.2.1.2.2", value: "Core 1"}
             ]}

          "1.3.6.1.4.1.2021.13.16.2.1.3" ->
            {:ok,
             [
               %{oid: "1.3.6.1.4.1.2021.13.16.2.1.3.1", value: 45_000},
               %{oid: "1.3.6.1.4.1.2021.13.16.2.1.3.2", value: 46_000}
             ]}

          "1.3.6.1.4.1.2021.13.16.3.1.2" ->
            {:ok, []}

          "1.3.6.1.4.1.2021.13.16.3.1.3" ->
            {:ok, []}

          "1.3.6.1.4.1.2021.13.16.4.1.2" ->
            {:ok, []}

          "1.3.6.1.4.1.2021.13.16.4.1.3" ->
            {:ok, []}

          _ ->
            {:error, :unknown_oid}
        end
      end)

      # Mock UCD-SNMP load and memory sensors
      expect(SnmpMock, :get, 5, fn _target, oid, _opts ->
        case oid do
          "1.3.6.1.4.1.2021.10.1.3.1" -> {:ok, 150}
          "1.3.6.1.4.1.2021.10.1.3.2" -> {:ok, 200}
          "1.3.6.1.4.1.2021.10.1.3.3" -> {:ok, 180}
          "1.3.6.1.4.1.2021.4.5.0" -> {:ok, 8_000_000}
          "1.3.6.1.4.1.2021.4.6.0" -> {:ok, 2_000_000}
          _ -> {:error, :unknown_oid}
        end
      end)

      result = NetSnmp.discover_sensors(@client_opts)

      assert {:ok, sensors} = result
      assert is_list(sensors)
      refute Enum.empty?(sensors)

      # Should have temperature sensors
      temp_sensors = Enum.filter(sensors, &(&1.sensor_type == "celsius"))
      assert length(temp_sensors) == 2

      # Should have load sensors
      load_sensors = Enum.filter(sensors, &(&1.sensor_type == "load"))
      assert length(load_sensors) == 3

      # Should have memory sensor
      memory_sensors = Enum.filter(sensors, &(&1.sensor_type == "memory"))
      assert length(memory_sensors) == 1
    end

    test "falls back to Base profile when Net-SNMP sensors not available" do
      # Mock walks returning empty for LM-SENSORS (6 walks)
      # and Base profile fallback (1 walk for ENTITY-SENSOR-MIB)
      expect(SnmpMock, :walk, 7, fn _target, _oid, _opts ->
        {:ok, []}
      end)

      # Mock get calls returning errors for UCD-SNMP
      # - 3 calls for load sensors
      # - 2 calls for memory sensors
      expect(SnmpMock, :get, 5, fn _target, _oid, _opts ->
        {:error, :no_such_object}
      end)

      result = NetSnmp.discover_sensors(@client_opts)

      # Should fall back to Base profile and return empty list
      assert {:ok, []} = result
    end
  end

  describe "identify_device/1" do
    test "identifies Ubuntu from sysDescr" do
      system_info = %{
        sys_descr: "Linux ubuntu 5.4.0-42-generic #46-Ubuntu SMP Fri Jul 10 00:24:02 UTC 2020 x86_64",
        sys_name: "ubuntu-server"
      }

      device_info = NetSnmp.identify_device(system_info)

      assert device_info.manufacturer == "Linux"
      assert device_info.model == "Ubuntu"
      assert is_nil(device_info.firmware_version)
    end

    test "identifies Ubuntu with version" do
      system_info = %{
        sys_descr: "Linux ubuntu Ubuntu/20.04 5.4.0-42-generic",
        sys_name: "ubuntu-server"
      }

      device_info = NetSnmp.identify_device(system_info)

      assert device_info.manufacturer == "Linux"
      assert device_info.model == "Ubuntu"
      assert device_info.firmware_version == "20.04"
    end

    test "identifies Debian from sysDescr" do
      system_info = %{
        sys_descr: "Linux debian Debian/10.5 4.19.0-10-amd64",
        sys_name: "debian-server"
      }

      device_info = NetSnmp.identify_device(system_info)

      assert device_info.manufacturer == "Linux"
      assert device_info.model == "Debian"
      assert device_info.firmware_version == "10.5"
    end

    test "identifies CentOS from sysDescr" do
      system_info = %{
        sys_descr: "Linux centos CentOS/7.8 3.10.0-1127.el7.x86_64",
        sys_name: "centos-server"
      }

      device_info = NetSnmp.identify_device(system_info)

      assert device_info.manufacturer == "Linux"
      assert device_info.model == "CentOS"
      assert device_info.firmware_version == "7.8"
    end

    test "identifies Red Hat from sysDescr" do
      system_info = %{
        sys_descr: "Linux rhel Red Hat/8.2 4.18.0-193.el8.x86_64",
        sys_name: "rhel-server"
      }

      device_info = NetSnmp.identify_device(system_info)

      assert device_info.manufacturer == "Linux"
      assert device_info.model == "Red Hat Enterprise Linux"
      assert device_info.firmware_version == "8.2"
    end

    test "identifies generic Linux from sysDescr" do
      system_info = %{
        sys_descr: "Linux server Linux/5.4.0-generic x86_64",
        sys_name: "linux-server"
      }

      device_info = NetSnmp.identify_device(system_info)

      assert device_info.manufacturer == "Linux"
      assert device_info.model == "Linux"
      # Regex only matches digits and dots
      assert device_info.firmware_version == "5.4.0"
    end

    test "identifies Unix for unknown systems" do
      system_info = %{
        sys_descr: "FreeBSD server 12.1-RELEASE",
        sys_name: "freebsd-server"
      }

      device_info = NetSnmp.identify_device(system_info)

      assert device_info.manufacturer == "Linux"
      assert device_info.model == "Unix"
      assert is_nil(device_info.firmware_version)
    end
  end

  describe "sensor discovery edge cases" do
    test "handles fan sensors" do
      # Mock walks for fan sensors
      expect(SnmpMock, :walk, 6, fn _target, oid, _opts ->
        case oid do
          "1.3.6.1.4.1.2021.13.16.2.1.2" ->
            {:ok, []}

          "1.3.6.1.4.1.2021.13.16.2.1.3" ->
            {:ok, []}

          "1.3.6.1.4.1.2021.13.16.3.1.2" ->
            {:ok, [%{oid: "1.3.6.1.4.1.2021.13.16.3.1.2.1", value: "System Fan"}]}

          "1.3.6.1.4.1.2021.13.16.3.1.3" ->
            {:ok, [%{oid: "1.3.6.1.4.1.2021.13.16.3.1.3.1", value: 2500}]}

          "1.3.6.1.4.1.2021.13.16.4.1.2" ->
            {:ok, []}

          "1.3.6.1.4.1.2021.13.16.4.1.3" ->
            {:ok, []}
        end
      end)

      # Mock UCD-SNMP sensors
      expect(SnmpMock, :get, 5, fn _target, oid, _opts ->
        case oid do
          "1.3.6.1.4.1.2021.10.1.3.1" -> {:ok, 100}
          "1.3.6.1.4.1.2021.10.1.3.2" -> {:ok, 150}
          "1.3.6.1.4.1.2021.10.1.3.3" -> {:ok, 120}
          "1.3.6.1.4.1.2021.4.5.0" -> {:ok, 16_000_000}
          "1.3.6.1.4.1.2021.4.6.0" -> {:ok, 4_000_000}
        end
      end)

      result = NetSnmp.discover_sensors(@client_opts)

      assert {:ok, sensors} = result
      fan_sensors = Enum.filter(sensors, &(&1.sensor_type == "rpm"))
      assert length(fan_sensors) == 1
      assert hd(fan_sensors).sensor_descr == "System Fan"
    end

    test "handles voltage sensors" do
      # Mock walks for voltage sensors
      expect(SnmpMock, :walk, 6, fn _target, oid, _opts ->
        case oid do
          "1.3.6.1.4.1.2021.13.16.2.1.2" ->
            {:ok, []}

          "1.3.6.1.4.1.2021.13.16.2.1.3" ->
            {:ok, []}

          "1.3.6.1.4.1.2021.13.16.3.1.2" ->
            {:ok, []}

          "1.3.6.1.4.1.2021.13.16.3.1.3" ->
            {:ok, []}

          "1.3.6.1.4.1.2021.13.16.4.1.2" ->
            {:ok, [%{oid: "1.3.6.1.4.1.2021.13.16.4.1.2.1", value: "VCore"}]}

          "1.3.6.1.4.1.2021.13.16.4.1.3" ->
            {:ok, [%{oid: "1.3.6.1.4.1.2021.13.16.4.1.3.1", value: 1_200}]}
        end
      end)

      # Mock UCD-SNMP sensors
      expect(SnmpMock, :get, 5, fn _target, oid, _opts ->
        case oid do
          "1.3.6.1.4.1.2021.10.1.3.1" -> {:ok, 80}
          "1.3.6.1.4.1.2021.10.1.3.2" -> {:ok, 90}
          "1.3.6.1.4.1.2021.10.1.3.3" -> {:ok, 85}
          "1.3.6.1.4.1.2021.4.5.0" -> {:ok, 32_000_000}
          "1.3.6.1.4.1.2021.4.6.0" -> {:ok, 8_000_000}
        end
      end)

      result = NetSnmp.discover_sensors(@client_opts)

      assert {:ok, sensors} = result
      voltage_sensors = Enum.filter(sensors, &(&1.sensor_type == "volts"))
      assert length(voltage_sensors) == 1
      assert hd(voltage_sensors).sensor_descr == "VCore"
    end
  end
end
