defmodule Towerops.Workers.DeviceMonitorWorkerTest do
  use Towerops.DataCase, async: false

  import Towerops.AccountsFixtures

  alias Towerops.Devices
  alias Towerops.Monitoring.PingMock
  alias Towerops.Organizations
  alias Towerops.Sites
  alias Towerops.Workers.DeviceMonitorWorker
  alias Towerops.Workers.PollingOffset

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

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

    %{organization: organization, site: site, user: user}
  end

  describe "start_monitoring/1" do
    test "schedules initial job with offset", %{site: site} do
      {:ok, device} =
        Devices.create_device(%{
          name: "Router 1",
          ip_address: "192.168.1.1",
          site_id: site.id,
          organization_id: site.organization_id,
          monitoring_enabled: true
        })

      # Get the expected offset (30 seconds is the monitor interval)
      expected_offset = PollingOffset.calculate_offset(device.id, 30)

      # Start monitoring
      assert {:ok, job} = DeviceMonitorWorker.start_monitoring(device.id)

      # Verify job is scheduled with offset
      assert job.args["device_id"] == device.id
      assert job.scheduled_at

      # Calculate the delay (scheduled_at - inserted_at)
      delay_seconds = DateTime.diff(job.scheduled_at, job.inserted_at, :second)
      assert delay_seconds == expected_offset
    end

    test "offset is within monitor interval bounds", %{site: site} do
      {:ok, device} =
        Devices.create_device(%{
          name: "Router 2",
          ip_address: "192.168.1.2",
          site_id: site.id,
          organization_id: site.organization_id,
          monitoring_enabled: true
        })

      # Start monitoring
      assert {:ok, job} = DeviceMonitorWorker.start_monitoring(device.id)

      # Verify offset is within bounds
      delay_seconds = DateTime.diff(job.scheduled_at, job.inserted_at, :second)
      assert delay_seconds >= 0
      assert delay_seconds < 30
    end
  end

  describe "stop_monitoring/1" do
    test "cancels all jobs for device", %{site: site} do
      {:ok, device} =
        Devices.create_device(%{
          name: "Router 3",
          ip_address: "192.168.1.3",
          site_id: site.id,
          organization_id: site.organization_id,
          monitoring_enabled: true
        })

      # Start monitoring
      assert {:ok, _job} = DeviceMonitorWorker.start_monitoring(device.id)

      # Stop monitoring
      assert {:ok, cancelled_jobs} = DeviceMonitorWorker.stop_monitoring(device.id)
      refute cancelled_jobs == []
    end
  end

  describe "perform/1" do
    test "skips check for deleted device" do
      fake_id = Ecto.UUID.generate()
      assert :ok == DeviceMonitorWorker.perform(%Oban.Job{args: %{"device_id" => fake_id}})
    end

    test "performs check and reschedules for monitoring-enabled device", %{site: site} do
      {:ok, device} =
        Devices.create_device(%{
          name: "Monitor Me",
          ip_address: "192.168.1.10",
          site_id: site.id,
          organization_id: site.organization_id,
          monitoring_enabled: true
        })

      Mox.expect(PingMock, :ping, fn _ip -> {:ok, 5.0} end)

      assert :ok == DeviceMonitorWorker.perform(%Oban.Job{args: %{"device_id" => device.id}})

      # Device should be marked up
      updated = Devices.get_device!(device.id)
      assert updated.status == :up
    end

    test "handles ping failure and marks device down", %{site: site} do
      {:ok, device} =
        Devices.create_device(%{
          name: "Failing Device",
          ip_address: "192.168.1.11",
          site_id: site.id,
          organization_id: site.organization_id,
          monitoring_enabled: true
        })

      Mox.expect(PingMock, :ping, fn _ip -> {:error, :timeout} end)

      assert :ok == DeviceMonitorWorker.perform(%Oban.Job{args: %{"device_id" => device.id}})

      updated = Devices.get_device!(device.id)
      assert updated.status == :down
    end

    test "does not check when monitoring is disabled", %{site: site} do
      {:ok, device} =
        Devices.create_device(%{
          name: "No Monitor",
          ip_address: "192.168.1.12",
          site_id: site.id,
          organization_id: site.organization_id,
          monitoring_enabled: false
        })

      # No mock expectation - ping should not be called
      assert :ok == DeviceMonitorWorker.perform(%Oban.Job{args: %{"device_id" => device.id}})
    end
  end

  describe "trigger_check/1" do
    test "inserts an immediate job", %{site: site} do
      {:ok, device} =
        Devices.create_device(%{
          name: "Trigger Me",
          ip_address: "192.168.1.13",
          site_id: site.id,
          organization_id: site.organization_id,
          monitoring_enabled: true
        })

      assert {:ok, job} = DeviceMonitorWorker.trigger_check(device.id)
      assert job.args["device_id"] == device.id
    end
  end
end
