defmodule Towerops.Snmp.Profiles.Vendors.AristaTest do
  use Towerops.DataCase, async: true

  import Mox

  alias Towerops.Snmp.Profiles.Vendors.Arista
  alias Towerops.Snmp.SnmpMock

  setup :verify_on_exit!

  @client_opts [
    ip: "192.168.1.1",
    community: "public",
    version: "2c",
    port: 161,
    timeout: 5000
  ]

  describe "profile_names/0" do
    test "returns Arista profile names" do
      assert Arista.profile_names() == ["arista_eos", "arista-mos"]
    end
  end

  describe "detect_hardware/1" do
    test "returns model when found in sysDescr" do
      expect(SnmpMock, :get, fn _, "1.3.6.1.2.1.1.1.0", _ ->
        {:ok, "Arista Networks EOS version 4.28.1F running on an Arista Networks DCS-7050TX-64"}
      end)

      assert Arista.detect_hardware(@client_opts) == "DCS-7050TX-64"
    end

    test "returns nil when model not in sysDescr" do
      expect(SnmpMock, :get, fn _, "1.3.6.1.2.1.1.1.0", _ ->
        {:ok, "Some other device"}
      end)

      assert Arista.detect_hardware(@client_opts) == nil
    end

    test "returns nil when SNMP fails" do
      expect(SnmpMock, :get, fn _, _, _ ->
        {:error, :timeout}
      end)

      assert Arista.detect_hardware(@client_opts) == nil
    end
  end

  describe "wireless_oid_defs/0" do
    test "returns list of sensor definitions" do
      defs = Arista.wireless_oid_defs()

      assert is_list(defs)
      assert [_ | _] = defs
    end

    test "includes CPU load sensor" do
      defs = Arista.wireless_oid_defs()
      cpu = Enum.find(defs, &(&1.sensor_descr == "CPU Load"))

      assert cpu
      assert cpu.sensor_type == "load"
      assert cpu.sensor_unit == "%"
    end
  end

  describe "discover_wireless_sensors/1" do
    test "discovers sensors when SNMP responds" do
      expect(SnmpMock, :get, 4, fn _, oid, _ ->
        cond do
          String.contains?(oid, "25.3.3.1.2.1") -> {:ok, 15}
          String.contains?(oid, "25.2.3.1.5.1") -> {:ok, 8_388_608}
          String.contains?(oid, "25.2.3.1.6.1") -> {:ok, 4_194_304}
          String.contains?(oid, "1.3.0") -> {:ok, 123_456_789}
          true -> {:error, :no_such_object}
        end
      end)

      sensors = Arista.discover_wireless_sensors(@client_opts)

      assert is_list(sensors)
      assert length(sensors) == 4
    end

    test "returns empty list when no sensors respond" do
      expect(SnmpMock, :get, 4, fn _, _, _ ->
        {:error, :no_such_object}
      end)

      sensors = Arista.discover_wireless_sensors(@client_opts)

      assert sensors == []
    end
  end

  describe "convert_dom_power_to_dbm/1" do
    test "converts DOM Rx Power from watts to dBm" do
      sensor = %{
        sensor_type: "power",
        sensor_descr: "DOM Rx Power",
        sensor_unit: "W",
        sensor_divisor: 1,
        last_value: 0.0005,
        metadata: %{}
      }

      result = Arista.convert_dom_power_to_dbm(sensor)

      assert result.sensor_type == "dbm"
      assert result.sensor_unit == "dBm"
      assert result.sensor_divisor == 1
      # 0.0005 watts = -3.010 dBm
      assert_in_delta result.last_value, -3.010, 0.001
      assert result.metadata.original_type == "power"
      assert result.metadata.original_value == 0.0005
    end

    test "converts DOM Tx Power from watts to dBm" do
      sensor = %{
        sensor_type: "power",
        sensor_descr: "DOM Tx Power",
        sensor_unit: "W",
        last_value: 0.001,
        metadata: %{}
      }

      result = Arista.convert_dom_power_to_dbm(sensor)

      assert result.sensor_type == "dbm"
      # 0.001 watts = 0 dBm
      assert result.last_value == 0.0
    end

    test "converts Xcvr Rx Power from watts to dBm" do
      sensor = %{
        sensor_type: "power",
        sensor_descr: "Xcvr Rx Power Sensor 1",
        last_value: 0.0001,
        metadata: %{}
      }

      result = Arista.convert_dom_power_to_dbm(sensor)

      assert result.sensor_type == "dbm"
      # 0.0001 watts = -10.0 dBm
      assert result.last_value == -10.0
    end

    test "does not convert non-DOM power sensors" do
      sensor = %{
        sensor_type: "power",
        sensor_descr: "PSU1 Power",
        last_value: 150.0
      }

      result = Arista.convert_dom_power_to_dbm(sensor)

      assert result.sensor_type == "power"
      assert result.last_value == 150.0
    end

    test "does not convert sensors with zero or negative values" do
      sensor = %{
        sensor_type: "power",
        sensor_descr: "DOM Rx Power",
        last_value: 0.0
      }

      result = Arista.convert_dom_power_to_dbm(sensor)

      # Should not convert 0 or negative values
      assert result.sensor_type == "power"
      assert result.last_value == 0.0
    end

    test "handles case-insensitive matching" do
      sensor = %{
        sensor_type: "power",
        sensor_descr: "dom rx power",
        last_value: 0.0005,
        metadata: %{}
      }

      result = Arista.convert_dom_power_to_dbm(sensor)

      assert result.sensor_type == "dbm"
    end
  end

  describe "apply_arista_thresholds/2" do
    test "applies thresholds from ARISTA-ENTITY-SENSOR-MIB" do
      sensors = [
        %{
          sensor_index: "1001",
          sensor_type: "temperature",
          sensor_descr: "Temp Sensor 1",
          last_value: 45.0
        }
      ]

      # Mock walking the Arista threshold table
      # Client.walk expects {:ok, list_of_maps_with_oid_and_value}
      expect(SnmpMock, :walk, fn _, "1.3.6.1.4.1.30065.3.3.1.1.1", _ ->
        {:ok,
         [
           # Low critical threshold (type 1)
           %{oid: "1.3.6.1.4.1.30065.3.3.1.1.1.1001.1", value: -10},
           # Low warning threshold (type 2)
           %{oid: "1.3.6.1.4.1.30065.3.3.1.1.1.1001.2", value: -5},
           # High warning threshold (type 3)
           %{oid: "1.3.6.1.4.1.30065.3.3.1.1.1.1001.3", value: 85},
           # High critical threshold (type 4)
           %{oid: "1.3.6.1.4.1.30065.3.3.1.1.1.1001.4", value: 95}
         ]}
      end)

      result = Arista.apply_arista_thresholds(sensors, @client_opts)

      assert [sensor] = result
      assert sensor.low_limit == -10
      assert sensor.low_warn_limit == -5
      assert sensor.warn_limit == 85
      assert sensor.high_limit == 95
    end

    test "converts thresholds for dBm sensors" do
      sensors = [
        %{
          sensor_index: "2001",
          sensor_type: "dbm",
          sensor_descr: "DOM Rx Power",
          last_value: -3.0
        }
      ]

      # Mock threshold in watts (needs conversion to dBm)
      expect(SnmpMock, :walk, fn _, _, _ ->
        {:ok,
         [
           # High warning: 0.002 watts = 3.01 dBm
           %{oid: "1.3.6.1.4.1.30065.3.3.1.1.1.2001.3", value: 0.002},
           # High critical: 0.003 watts = 4.77 dBm
           %{oid: "1.3.6.1.4.1.30065.3.3.1.1.1.2001.4", value: 0.003}
         ]}
      end)

      result = Arista.apply_arista_thresholds(sensors, @client_opts)

      assert [sensor] = result
      assert_in_delta sensor.warn_limit, 3.01, 0.01
      assert_in_delta sensor.high_limit, 4.77, 0.01
    end

    test "returns sensors unchanged when no thresholds available" do
      sensors = [
        %{sensor_index: "1001", sensor_type: "temperature", last_value: 45.0}
      ]

      expect(SnmpMock, :walk, fn _, _, _ ->
        {:ok, []}
      end)

      result = Arista.apply_arista_thresholds(sensors, @client_opts)

      assert result == sensors
    end

    test "returns sensors unchanged when SNMP walk fails" do
      sensors = [
        %{sensor_index: "1001", sensor_type: "temperature", last_value: 45.0}
      ]

      expect(SnmpMock, :walk, fn _, _, _ ->
        {:error, :timeout}
      end)

      result = Arista.apply_arista_thresholds(sensors, @client_opts)

      assert result == sensors
    end
  end

  describe "apply_smart_grouping/1" do
    test "groups SFP sensors" do
      sensor = %{sensor_descr: "DOM Rx Power Sensor 1"}
      result = Arista.apply_smart_grouping(sensor)
      assert result.sensor_group == "SFPs"
    end

    test "groups Xcvr sensors" do
      sensor = %{sensor_descr: "Xcvr Temp Sensor 1"}
      result = Arista.apply_smart_grouping(sensor)
      assert result.sensor_group == "SFPs"
    end

    test "groups power connector sensors" do
      sensor = %{sensor_descr: "PwrCon1 Temp"}
      result = Arista.apply_smart_grouping(sensor)
      assert result.sensor_group == "PwrCon1"
    end

    test "groups PSU sensors" do
      sensor = %{sensor_descr: "PowerSupply1 Voltage"}
      result = Arista.apply_smart_grouping(sensor)
      assert result.sensor_group == "PSUs"
    end

    test "groups platform chipset sensors" do
      sensor = %{sensor_descr: "Trident Temp Sensor"}
      result = Arista.apply_smart_grouping(sensor)
      assert result.sensor_group == "Platform"

      sensor2 = %{sensor_descr: "Jericho Temperature"}
      result2 = Arista.apply_smart_grouping(sensor2)
      assert result2.sensor_group == "Platform"
    end

    test "groups other sensors as System" do
      sensor = %{sensor_descr: "Ambient Temperature"}
      result = Arista.apply_smart_grouping(sensor)
      assert result.sensor_group == "System"
    end
  end

  describe "cleanup_description/1" do
    test "removes trailing sensor text" do
      sensor = %{sensor_descr: "Temp Sensor 1 sensor"}
      result = Arista.cleanup_description(sensor)
      assert result.sensor_descr == "Temp Sensor 1"
    end

    test "removes duplicate Sensor strings" do
      sensor = %{sensor_descr: "Sensor VoltageSensor1"}
      result = Arista.cleanup_description(sensor)
      assert result.sensor_descr == "Voltage1"
    end

    test "simplifies PSU naming" do
      sensor = %{sensor_descr: "PowerSupply1/1 Sensor VoltageSensor1"}
      result = Arista.cleanup_description(sensor)
      # The regex removes "Sensor " and "Sensor1" but keeps "/1"
      # This is acceptable - main goal is removing duplicate "Sensor" text
      assert result.sensor_descr == "PowerSupply1/1 Voltage1"
    end

    test "removes hotspot text" do
      sensor = %{sensor_descr: "Temp hotspot Sensor 1"}
      result = Arista.cleanup_description(sensor)
      assert result.sensor_descr == "Temp Sensor 1"
    end

    test "cleans up whitespace" do
      sensor = %{sensor_descr: "Temp   Sensor  1"}
      result = Arista.cleanup_description(sensor)
      assert result.sensor_descr == "Temp Sensor 1"
    end
  end

  describe "post_process_sensors/2" do
    test "applies all enhancements in correct order" do
      sensors = [
        %{
          sensor_type: "power",
          sensor_descr: "DOM Rx Power sensor",
          sensor_index: "1001",
          last_value: 0.0005,
          metadata: %{}
        }
      ]

      # Mock threshold table
      expect(SnmpMock, :walk, fn _, _, _ ->
        {:ok,
         [
           %{oid: "1.3.6.1.4.1.30065.3.3.1.1.1.1001.3", value: 0.002},
           %{oid: "1.3.6.1.4.1.30065.3.3.1.1.1.1001.4", value: 0.003}
         ]}
      end)

      result = Arista.post_process_sensors(sensors, @client_opts)

      assert [sensor] = result
      # DOM conversion applied
      assert sensor.sensor_type == "dbm"
      assert_in_delta sensor.last_value, -3.010, 0.001
      # Thresholds applied (converted to dBm)
      assert sensor.warn_limit
      assert sensor.high_limit
      # Grouping applied
      assert sensor.sensor_group == "SFPs"
      # Description cleaned
      assert sensor.sensor_descr == "DOM Rx Power"
    end
  end
end
