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

  import Mox

  alias Towerops.Snmp.Profiles.Vendors.Powervault
  alias Towerops.Snmp.SnmpMock

  setup :verify_on_exit!

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

  describe "profile_names/0" do
    test "returns dell-powervault and powervault profiles" do
      assert Powervault.profile_names() == ["dell-powervault", "powervault"]
    end
  end

  describe "detect_hardware/1" do
    test "returns nil (no hardware detection)" do
      assert Powervault.detect_hardware(@client_opts) == nil
    end
  end

  describe "discover_wireless_sensors/1" do
    test "returns empty list (no wireless sensors)" do
      assert Powervault.discover_wireless_sensors(@client_opts) == []
    end
  end

  describe "wireless_oid_defs/0" do
    test "returns empty list (no wireless OIDs)" do
      assert Powervault.wireless_oid_defs() == []
    end
  end

  describe "post_process_sensors/2" do
    test "parses temperature sensors from text messages" do
      # Mock SNMP walk returning temperature message
      expect(SnmpMock, :walk, fn _target, _oid, _opts ->
        {:ok,
         [
           %{oid: "1.3.6.1.3.94.1.8.1.6.1", value: "Enclosure Temp: 25 C 77.0F"}
         ]}
      end)

      sensors = []
      result = Powervault.post_process_sensors(sensors, @client_opts)

      assert length(result) == 1
      [sensor] = result

      assert sensor.sensor_type == "temperature"
      assert sensor.sensor_descr == "Enclosure Temp"
      assert sensor.sensor_unit == "°C"
      assert sensor.sensor_oid == "#{@sensor_message_oid}.1"
      assert sensor.sensor_index == "powervault_temp.1"
      assert sensor.last_value == 25
      assert sensor.sensor_divisor == 1
      assert sensor.metadata.vendor == "dell_powervault"
      assert sensor.metadata.message_type == "text_parsed"
    end

    test "parses voltage sensors from text messages" do
      expect(SnmpMock, :walk, fn _, @sensor_message_oid, _ ->
        {:ok,
         [
           %{oid: "#{@sensor_message_oid}.2", value: "12V Rail Voltage: 12.1V"}
         ]}
      end)

      sensors = []
      result = Powervault.post_process_sensors(sensors, @client_opts)

      assert length(result) == 1
      [sensor] = result

      assert sensor.sensor_type == "voltage"
      assert sensor.sensor_descr == "12V Rail Voltage"
      assert sensor.sensor_unit == "V"
      assert sensor.sensor_oid == "#{@sensor_message_oid}.2"
      assert sensor.sensor_index == "powervault_voltage.2"
      assert sensor.last_value == 12.1
      assert sensor.sensor_divisor == 1
    end

    test "parses current sensors from text messages" do
      expect(SnmpMock, :walk, fn _, @sensor_message_oid, _ ->
        {:ok,
         [
           %{oid: "#{@sensor_message_oid}.3", value: "Battery Current: 0.5A"}
         ]}
      end)

      sensors = []
      result = Powervault.post_process_sensors(sensors, @client_opts)

      assert length(result) == 1
      [sensor] = result

      assert sensor.sensor_type == "current"
      assert sensor.sensor_descr == "Battery Current"
      assert sensor.sensor_unit == "A"
      assert sensor.sensor_oid == "#{@sensor_message_oid}.3"
      assert sensor.sensor_index == "powervault_current.3"
      assert sensor.last_value == 0.5
      assert sensor.sensor_divisor == 1
    end

    test "parses battery charge sensors from text messages" do
      expect(SnmpMock, :walk, fn _, @sensor_message_oid, _ ->
        {:ok,
         [
           %{oid: "#{@sensor_message_oid}.4", value: "Battery Charge: 95%"}
         ]}
      end)

      sensors = []
      result = Powervault.post_process_sensors(sensors, @client_opts)

      assert length(result) == 1
      [sensor] = result

      assert sensor.sensor_type == "percent"
      assert sensor.sensor_descr == "Battery Charge"
      assert sensor.sensor_unit == "%"
      assert sensor.sensor_oid == "#{@sensor_message_oid}.4"
      assert sensor.sensor_index == "powervault_charge.4"
      assert sensor.last_value == 95
      assert sensor.sensor_divisor == 1
      assert sensor.high_limit == 100
      assert sensor.low_warn_limit == 20
      assert sensor.low_limit == 10
    end

    test "parses multiple sensors from mixed message types" do
      expect(SnmpMock, :walk, fn _, @sensor_message_oid, _ ->
        {:ok,
         [
           %{oid: "#{@sensor_message_oid}.1", value: "Enclosure Temp: 25 C 77.0F"},
           %{oid: "#{@sensor_message_oid}.2", value: "12V Rail Voltage: 12.1V"},
           %{oid: "#{@sensor_message_oid}.3", value: "Battery Current: 0.5A"},
           %{oid: "#{@sensor_message_oid}.4", value: "Battery Charge: 95%"}
         ]}
      end)

      sensors = []
      result = Powervault.post_process_sensors(sensors, @client_opts)

      assert length(result) == 4

      types = Enum.map(result, & &1.sensor_type)
      assert "temperature" in types
      assert "voltage" in types
      assert "current" in types
      assert "percent" in types
    end

    test "combines parsed sensors with existing sensors" do
      expect(SnmpMock, :walk, fn _, @sensor_message_oid, _ ->
        {:ok,
         [
           %{oid: "#{@sensor_message_oid}.1", value: "Enclosure Temp: 25 C 77.0F"}
         ]}
      end)

      existing_sensors = [
        %{
          sensor_type: "power",
          sensor_descr: "Power Supply",
          sensor_oid: "1.2.3.4.5",
          sensor_index: "power_1",
          last_value: 500
        }
      ]

      result = Powervault.post_process_sensors(existing_sensors, @client_opts)

      assert length(result) == 2

      # Original sensor preserved
      assert Enum.any?(result, &(&1.sensor_type == "power"))

      # New temperature sensor added
      assert Enum.any?(result, &(&1.sensor_type == "temperature"))
    end

    test "returns unchanged sensors when walk fails" do
      expect(SnmpMock, :walk, fn _, @sensor_message_oid, _ ->
        {:error, :timeout}
      end)

      existing_sensors = [
        %{
          sensor_type: "power",
          sensor_descr: "Power Supply",
          sensor_oid: "1.2.3.4.5",
          sensor_index: "power_1",
          last_value: 500
        }
      ]

      result = Powervault.post_process_sensors(existing_sensors, @client_opts)

      assert result == existing_sensors
    end

    test "returns unchanged sensors when walk returns empty list" do
      expect(SnmpMock, :walk, fn _, @sensor_message_oid, _ ->
        {:ok, []}
      end)

      existing_sensors = [
        %{
          sensor_type: "power",
          sensor_descr: "Power Supply",
          sensor_oid: "1.2.3.4.5",
          sensor_index: "power_1",
          last_value: 500
        }
      ]

      result = Powervault.post_process_sensors(existing_sensors, @client_opts)

      assert result == existing_sensors
    end

    test "skips messages without colon separator" do
      expect(SnmpMock, :walk, fn _, @sensor_message_oid, _ ->
        {:ok,
         [
           %{oid: "#{@sensor_message_oid}.1", value: "Invalid message format"},
           %{oid: "#{@sensor_message_oid}.2", value: "12V Rail Voltage: 12.1V"}
         ]}
      end)

      sensors = []
      result = Powervault.post_process_sensors(sensors, @client_opts)

      # Only the valid voltage sensor should be parsed
      assert length(result) == 1
      assert hd(result).sensor_type == "voltage"
    end

    test "skips messages that don't match any sensor pattern" do
      expect(SnmpMock, :walk, fn _, @sensor_message_oid, _ ->
        {:ok,
         [
           %{oid: "#{@sensor_message_oid}.1", value: "Unknown Sensor: 123"},
           %{oid: "#{@sensor_message_oid}.2", value: "Battery Charge: 95%"}
         ]}
      end)

      sensors = []
      result = Powervault.post_process_sensors(sensors, @client_opts)

      # Only the battery charge sensor should be parsed
      assert length(result) == 1
      assert hd(result).sensor_type == "percent"
    end

    test "skips non-string message values" do
      expect(SnmpMock, :walk, fn _, @sensor_message_oid, _ ->
        {:ok,
         [
           %{oid: "#{@sensor_message_oid}.1", value: 12_345},
           %{oid: "#{@sensor_message_oid}.2", value: "Battery Charge: 95%"}
         ]}
      end)

      sensors = []
      result = Powervault.post_process_sensors(sensors, @client_opts)

      # Only the battery charge sensor should be parsed
      assert length(result) == 1
      assert hd(result).sensor_type == "percent"
    end

    test "handles temperature messages with different formats" do
      expect(SnmpMock, :walk, fn _, @sensor_message_oid, _ ->
        {:ok,
         [
           %{oid: "#{@sensor_message_oid}.1", value: "CPU Temp: 45 C 113.0F"},
           %{oid: "#{@sensor_message_oid}.2", value: "Board Temp: 30 C 86.0F"}
         ]}
      end)

      sensors = []
      result = Powervault.post_process_sensors(sensors, @client_opts)

      assert length(result) == 2

      [sensor1, sensor2] = result
      assert sensor1.sensor_descr == "CPU Temp"
      assert sensor1.last_value == 45
      assert sensor2.sensor_descr == "Board Temp"
      assert sensor2.last_value == 30
    end

    test "handles voltage messages with different rail names" do
      expect(SnmpMock, :walk, fn _, @sensor_message_oid, _ ->
        {:ok,
         [
           %{oid: "#{@sensor_message_oid}.1", value: "5V Rail Voltage: 5.2V"},
           %{oid: "#{@sensor_message_oid}.2", value: "3.3V Rail Voltage: 3.4V"}
         ]}
      end)

      sensors = []
      result = Powervault.post_process_sensors(sensors, @client_opts)

      assert length(result) == 2

      [sensor1, sensor2] = result
      assert sensor1.sensor_descr == "5V Rail Voltage"
      assert sensor1.last_value == 5.2
      assert sensor2.sensor_descr == "3.3V Rail Voltage"
      assert sensor2.last_value == 3.4
    end

    test "rejects temperature messages with invalid format" do
      expect(SnmpMock, :walk, fn _, @sensor_message_oid, _ ->
        {:ok,
         [
           # Missing space before C
           %{oid: "#{@sensor_message_oid}.1", value: "Enclosure Temp: 25C"},
           # Wrong unit format
           %{oid: "#{@sensor_message_oid}.2", value: "Enclosure Temp: 25 Celsius"}
         ]}
      end)

      sensors = []
      result = Powervault.post_process_sensors(sensors, @client_opts)

      assert result == []
    end

    test "rejects voltage messages with invalid format" do
      expect(SnmpMock, :walk, fn _, @sensor_message_oid, _ ->
        {:ok,
         [
           # Space before V
           %{oid: "#{@sensor_message_oid}.1", value: "12V Rail Voltage: 12.1 V"},
           # Wrong unit
           %{oid: "#{@sensor_message_oid}.2", value: "12V Rail Voltage: 12.1 Volts"}
         ]}
      end)

      sensors = []
      result = Powervault.post_process_sensors(sensors, @client_opts)

      assert result == []
    end

    test "rejects current messages with invalid format" do
      expect(SnmpMock, :walk, fn _, @sensor_message_oid, _ ->
        {:ok,
         [
           # Space before A
           %{oid: "#{@sensor_message_oid}.1", value: "Battery Current: 0.5 A"},
           # Wrong unit
           %{oid: "#{@sensor_message_oid}.2", value: "Battery Current: 0.5 Amps"}
         ]}
      end)

      sensors = []
      result = Powervault.post_process_sensors(sensors, @client_opts)

      assert result == []
    end

    test "rejects charge messages with invalid format" do
      expect(SnmpMock, :walk, fn _, @sensor_message_oid, _ ->
        {:ok,
         [
           # Space before %
           %{oid: "#{@sensor_message_oid}.1", value: "Battery Charge: 95 %"},
           # Wrong unit
           %{oid: "#{@sensor_message_oid}.2", value: "Battery Charge: 95 percent"}
         ]}
      end)

      sensors = []
      result = Powervault.post_process_sensors(sensors, @client_opts)

      assert result == []
    end
  end
end
