defmodule Towerops.Monitoring.Executors.SnmpSensorExecutorTest do
  use Towerops.DataCase

  import Mox
  import Towerops.AccountsFixtures

  alias Towerops.Monitoring
  alias Towerops.Monitoring.Executors.SnmpSensorExecutor
  alias Towerops.Snmp
  alias Towerops.Snmp.Sensor
  alias Towerops.Snmp.SnmpMock

  setup :verify_on_exit!

  setup do
    user = user_fixture()
    {:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)

    {:ok, site} =
      Towerops.Sites.create_site(%{
        name: "Test Site",
        organization_id: organization.id
      })

    {:ok, device} =
      Towerops.Devices.create_device(%{
        name: "Test Router",
        ip_address: "192.168.1.1",
        snmp_enabled: true,
        snmp_version: "2c",
        snmp_community: "public",
        snmp_port: 161,
        site_id: site.id,
        organization_id: organization.id
      })

    # Reload device to get default values
    device = Towerops.Devices.get_device!(device.id)

    snmp_device =
      %Snmp.Device{}
      |> Snmp.Device.changeset(%{
        device_id: device.id,
        sys_name: "test-router",
        sys_descr: "Test Device"
      })
      |> Repo.insert!()

    %{device: device, snmp_device: snmp_device, organization: organization}
  end

  describe "execute/1" do
    test "returns standardized format with value, status, output, response_time_ms", %{
      device: device,
      snmp_device: snmp_device
    } do
      # Create a sensor
      sensor =
        %Sensor{}
        |> Sensor.changeset(%{
          snmp_device_id: snmp_device.id,
          sensor_descr: "CPU Temperature",
          sensor_type: "temperature",
          sensor_index: "1",
          sensor_oid: ".1.3.6.1.4.1.9.9.13.1.3.1.3.1",
          sensor_unit: "°C",
          sensor_divisor: 1
        })
        |> Repo.insert!()

      # Create a check for the sensor
      {:ok, check} =
        Monitoring.create_check(%{
          organization_id: device.organization_id,
          device_id: device.id,
          name: "CPU Temperature",
          check_type: "snmp_sensor",
          source_type: "auto_discovery",
          source_id: sensor.id,
          interval_seconds: 60,
          enabled: true,
          config: %{
            "sensor_type" => "temperature",
            "sensor_oid" => ".1.3.6.1.4.1.9.9.13.1.3.1.3.1",
            "sensor_unit" => "°C"
          }
        })

      # Mock SNMP response
      expect(SnmpMock, :get, fn _ip, _oid, _opts ->
        {:ok, {:integer, 42}}
      end)

      # Execute the check
      result = SnmpSensorExecutor.execute(check)

      # Verify standardized format
      assert {:ok, response} = result
      assert is_float(response.value) or is_nil(response.value)
      assert response.status in [0, 1, 2, 3]
      assert is_binary(response.output)
      assert is_number(response.response_time_ms) or is_nil(response.response_time_ms)
    end

    test "returns OK status for normal sensor value", %{device: device, snmp_device: snmp_device} do
      sensor =
        %Sensor{}
        |> Sensor.changeset(%{
          snmp_device_id: snmp_device.id,
          sensor_descr: "PSU Voltage",
          sensor_type: "voltage",
          sensor_index: "1",
          sensor_oid: ".1.2.3.4",
          sensor_unit: "V",
          sensor_divisor: 10,
          metadata: %{
            "limit_low" => 110.0,
            "limit_high" => 130.0
          }
        })
        |> Repo.insert!()

      {:ok, check} =
        Monitoring.create_check(%{
          organization_id: device.organization_id,
          device_id: device.id,
          name: "PSU Voltage",
          check_type: "snmp_sensor",
          source_type: "auto_discovery",
          source_id: sensor.id,
          config: %{
            "sensor_type" => "voltage",
            "sensor_oid" => ".1.2.3.4",
            "sensor_unit" => "V"
          }
        })

      expect(SnmpMock, :get, fn _ip, _oid, _opts ->
        {:ok, {:integer, 1200}}
      end)

      assert {:ok, response} = SnmpSensorExecutor.execute(check)
      assert response.value == 120.0
      assert response.status == 0
      assert response.output =~ "PSU Voltage"
      assert response.output =~ "120.0"
    end

    test "returns CRITICAL status for out-of-range sensor value", %{
      device: device,
      snmp_device: snmp_device
    } do
      sensor =
        %Sensor{}
        |> Sensor.changeset(%{
          snmp_device_id: snmp_device.id,
          sensor_descr: "CPU Temperature",
          sensor_type: "temperature",
          sensor_index: "1",
          sensor_oid: ".1.2.3.4",
          sensor_unit: "°C",
          sensor_divisor: 1,
          metadata: %{
            "limit_low" => 0.0,
            "limit_high" => 80.0
          }
        })
        |> Repo.insert!()

      {:ok, check} =
        Monitoring.create_check(%{
          organization_id: device.organization_id,
          device_id: device.id,
          name: "CPU Temperature",
          check_type: "snmp_sensor",
          source_type: "auto_discovery",
          source_id: sensor.id,
          config: %{
            "sensor_type" => "temperature",
            "sensor_oid" => ".1.2.3.4",
            "sensor_unit" => "°C"
          }
        })

      expect(SnmpMock, :get, fn _ip, _oid, _opts ->
        {:ok, {:integer, 95}}
      end)

      assert {:ok, response} = SnmpSensorExecutor.execute(check)
      assert response.value == 95.0
      assert response.status == 2
    end

    test "returns error when sensor not found", %{device: device} do
      # Create check with non-existent source_id
      {:ok, check} =
        Monitoring.create_check(%{
          organization_id: device.organization_id,
          device_id: device.id,
          name: "Missing Sensor",
          check_type: "snmp_sensor",
          source_type: "auto_discovery",
          source_id: Ecto.UUID.generate(),
          config: %{
            "sensor_type" => "temperature",
            "sensor_oid" => ".1.2.3.4",
            "sensor_unit" => "°C"
          }
        })

      assert {:error, error_msg} = SnmpSensorExecutor.execute(check)
      assert error_msg =~ "Sensor not found"
    end

    test "returns error when SNMP fails", %{device: device, snmp_device: snmp_device} do
      sensor =
        %Sensor{}
        |> Sensor.changeset(%{
          snmp_device_id: snmp_device.id,
          sensor_descr: "Test Sensor",
          sensor_type: "temperature",
          sensor_index: "1",
          sensor_oid: ".1.2.3.4",
          sensor_unit: "°C",
          sensor_divisor: 1
        })
        |> Repo.insert!()

      {:ok, check} =
        Monitoring.create_check(%{
          organization_id: device.organization_id,
          device_id: device.id,
          name: "Test Sensor",
          check_type: "snmp_sensor",
          source_type: "auto_discovery",
          source_id: sensor.id,
          config: %{
            "sensor_type" => "temperature",
            "sensor_oid" => ".1.2.3.4",
            "sensor_unit" => "°C"
          }
        })

      expect(SnmpMock, :get, fn _ip, _oid, _opts ->
        {:error, :timeout}
      end)

      assert {:error, :timeout} = SnmpSensorExecutor.execute(check)
    end
  end
end
