defmodule Towerops.Workers.CheckExecutorWorkerTest do
  use Towerops.DataCase, async: false
  use Oban.Testing, repo: Towerops.Repo

  import Mox
  import Towerops.AccountsFixtures

  alias Towerops.Monitoring
  alias Towerops.Monitoring.CheckResult
  alias Towerops.Repo
  alias Towerops.Snmp.Device
  alias Towerops.Snmp.Sensor
  alias Towerops.Snmp.SnmpMock
  alias Towerops.Workers.CheckExecutorWorker

  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
      })

    device = Towerops.Devices.get_device!(device.id)

    snmp_device =
      %Device{}
      |> 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 "perform/1 - dispatcher" do
    test "dispatches snmp_sensor checks to SnmpSensorExecutor", %{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.3.6.1.4.1.9.9.13.1.3.1.3.1",
          sensor_unit: "°C",
          sensor_divisor: 1
        })
        |> 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,
          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 worker
      assert :ok = perform_job(CheckExecutorWorker, %{check_id: check.id})

      # Verify result was recorded
      results = Repo.all(from(r in CheckResult, where: r.check_id == ^check.id))
      assert length(results) == 1
      result = hd(results)
      assert result.value == 42.0
      assert result.status == 0
      assert result.output =~ "CPU Temperature"
    end

    test "dispatches http checks with adapter", %{device: device} do
      {:ok, check} =
        Monitoring.create_check(%{
          organization_id: device.organization_id,
          device_id: device.id,
          name: "Web Check",
          check_type: "http",
          source_type: "manual",
          interval_seconds: 60,
          enabled: true,
          config: %{
            "url" => "https://example.com",
            "method" => "GET",
            "expected_status" => "200"
          }
        })

      # Mock HTTP executor (assuming it exists and works)
      # For now, test will handle unknown executor error
      assert :ok = perform_job(CheckExecutorWorker, %{check_id: check.id})

      # Should record error result since HTTP executor not mocked
      results = Repo.all(from(r in CheckResult, where: r.check_id == ^check.id))
      assert length(results) == 1
      result = hd(results)
      # UNKNOWN
      assert result.status == 3
      assert result.output =~ "Error"
    end

    # Note: Cannot test unknown check type because schema validation prevents
    # creating checks with invalid check_type values
  end

  describe "perform/1 - result recording" do
    test "records successful check result in check_results table", %{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
        })
        |> 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,
          interval_seconds: 60,
          enabled: true,
          config: %{}
        })

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

      assert :ok = perform_job(CheckExecutorWorker, %{check_id: check.id})

      # Verify all fields in check_result
      result = Repo.one!(from(r in CheckResult, where: r.check_id == ^check.id))
      assert result.check_id == check.id
      assert result.organization_id == device.organization_id
      assert result.value == 120.0
      assert result.status == 0
      assert result.output =~ "PSU Voltage"
      assert is_number(result.response_time_ms) or is_nil(result.response_time_ms)
      assert %DateTime{} = result.checked_at
      assert is_nil(result.agent_token_id)
    end

    test "records error as UNKNOWN status", %{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,
          interval_seconds: 60,
          enabled: true,
          config: %{}
        })

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

      assert :ok = perform_job(CheckExecutorWorker, %{check_id: check.id})

      result = Repo.one!(from(r in CheckResult, where: r.check_id == ^check.id))
      # UNKNOWN
      assert result.status == 3
      assert result.output =~ "Error"
      assert is_nil(result.value)
    end
  end

  # Note: Check state updates are tested separately in monitoring_test.exs
  # This worker test focuses on dispatcher and result recording

  describe "perform/1 - scheduling" do
    test "schedules next check execution", %{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,
          interval_seconds: 120,
          enabled: true,
          config: %{}
        })

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

      # Clear existing jobs
      Repo.delete_all(Oban.Job)

      assert :ok = perform_job(CheckExecutorWorker, %{check_id: check.id})

      # Verify next job was scheduled
      jobs = Repo.all(from j in Oban.Job, where: j.worker == "Towerops.Workers.CheckExecutorWorker")
      assert length(jobs) == 1
      job = hd(jobs)
      assert job.args["check_id"] == check.id
      assert job.scheduled_at
    end
  end

  describe "perform/1 - check state handling" do
    test "skips execution when check is disabled", %{device: device, snmp_device: snmp_device} do
      sensor =
        %Sensor{}
        |> Sensor.changeset(%{
          snmp_device_id: snmp_device.id,
          sensor_descr: "Disabled 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: "Disabled Check",
          check_type: "snmp_sensor",
          source_type: "auto_discovery",
          source_id: sensor.id,
          interval_seconds: 60,
          # Disabled
          enabled: false,
          config: %{}
        })

      # Should not call SNMP executor
      assert :ok = perform_job(CheckExecutorWorker, %{check_id: check.id})

      # No results should be recorded
      results = Repo.all(from(r in CheckResult, where: r.check_id == ^check.id))
      assert results == []
    end

    test "returns ok when check is deleted", %{} do
      # Non-existent check ID
      assert :ok = perform_job(CheckExecutorWorker, %{check_id: Ecto.UUID.generate()})
    end
  end
end
