defmodule Towerops.MonitoringTest do
  use Towerops.DataCase

  alias Towerops.Monitoring

  describe "monitoring_checks" do
    import Towerops.AccountsFixtures

    alias Towerops.Monitoring.Check

    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: "Router 1",
          ip_address: "192.168.1.1",
          site_id: site.id
        })

      %{device: device}
    end

    @valid_attrs %{
      status: :success,
      response_time_ms: 50,
      checked_at: ~U[2025-12-21 12:00:00Z]
    }

    test "create_check/1 with valid data creates a check", %{device: device} do
      attrs = Map.put(@valid_attrs, :device_id, device.id)
      assert {:ok, %Check{} = check} = Monitoring.create_check(attrs)
      assert check.status == :success
      assert check.response_time_ms == 50
    end

    test "create_check/1 with invalid data returns error changeset" do
      assert {:error, %Ecto.Changeset{}} = Monitoring.create_check(%{})
    end

    test "list_devices_checks/2 returns checks for device", %{device: device} do
      attrs = Map.put(@valid_attrs, :device_id, device.id)
      {:ok, check} = Monitoring.create_check(attrs)
      assert Monitoring.list_devices_checks(device.id) == [check]
    end

    test "list_devices_checks/2 limits results", %{device: device} do
      # Create 10 checks
      for i <- 1..10 do
        attrs =
          @valid_attrs
          |> Map.put(:device_id, device.id)
          |> Map.put(:checked_at, DateTime.add(~U[2025-12-21 12:00:00Z], i, :second))

        {:ok, _check} = Monitoring.create_check(attrs)
      end

      assert length(Monitoring.list_devices_checks(device.id, 5)) == 5
    end

    test "get_latest_check/1 returns most recent check", %{device: device} do
      attrs1 =
        @valid_attrs
        |> Map.put(:device_id, device.id)
        |> Map.put(:checked_at, ~U[2025-12-21 12:00:00Z])

      attrs2 =
        @valid_attrs
        |> Map.put(:device_id, device.id)
        |> Map.put(:checked_at, ~U[2025-12-21 13:00:00Z])

      {:ok, _check1} = Monitoring.create_check(attrs1)
      {:ok, check2} = Monitoring.create_check(attrs2)

      latest = Monitoring.get_latest_check(device.id)
      assert latest.id == check2.id
    end

    test "get_latest_check/1 returns nil when no checks exist", %{device: device} do
      assert Monitoring.get_latest_check(device.id) == nil
    end

    test "delete_old_checks/1 deletes checks older than date", %{device: device} do
      old_attrs =
        @valid_attrs
        |> Map.put(:device_id, device.id)
        |> Map.put(:checked_at, ~U[2025-01-01 12:00:00Z])

      new_attrs =
        @valid_attrs
        |> Map.put(:device_id, device.id)
        |> Map.put(:checked_at, ~U[2025-12-21 12:00:00Z])

      {:ok, _old_check} = Monitoring.create_check(old_attrs)
      {:ok, _new_check} = Monitoring.create_check(new_attrs)

      {deleted, _} = Monitoring.delete_old_checks(~U[2025-06-01 00:00:00Z])
      assert deleted == 1
      assert length(Monitoring.list_devices_checks(device.id)) == 1
    end

    test "get_hourly_stats/3 returns stats for device", %{device: device} do
      base_time = ~U[2025-12-21 12:00:00Z]

      # Create checks in two different hours
      {:ok, _} =
        Monitoring.create_check(%{
          device_id: device.id,
          status: :success,
          response_time_ms: 50,
          checked_at: base_time
        })

      {:ok, _} =
        Monitoring.create_check(%{
          device_id: device.id,
          status: :success,
          response_time_ms: 100,
          checked_at: DateTime.add(base_time, 30 * 60, :second)
        })

      {:ok, _} =
        Monitoring.create_check(%{
          device_id: device.id,
          status: :failure,
          response_time_ms: nil,
          checked_at: DateTime.add(base_time, 3600, :second)
        })

      start_time = DateTime.add(base_time, -3600, :second)
      end_time = DateTime.add(base_time, 7200, :second)

      result = Monitoring.get_hourly_stats(device.id, start_time, end_time)
      assert %Postgrex.Result{} = result
      assert length(result.rows) == 2

      # First hour should have 2 successful checks
      [first_hour | _] = result.rows
      [_bucket, total, successful, failed, avg_response, min_response, max_response, uptime] = first_hour
      assert total == 2
      assert successful == 2
      assert failed == 0
      assert avg_response == Decimal.new("75.00")
      assert min_response == 50
      assert max_response == 100
      assert uptime == Decimal.new("100.00")
    end

    test "get_daily_stats/3 returns stats for device", %{device: device} do
      day1 = ~U[2025-12-20 12:00:00Z]
      day2 = ~U[2025-12-21 12:00:00Z]

      # Create checks on two different days
      {:ok, _} =
        Monitoring.create_check(%{
          device_id: device.id,
          status: :success,
          response_time_ms: 50,
          checked_at: day1
        })

      {:ok, _} =
        Monitoring.create_check(%{
          device_id: device.id,
          status: :success,
          response_time_ms: 60,
          checked_at: DateTime.add(day1, 3600, :second)
        })

      {:ok, _} =
        Monitoring.create_check(%{
          device_id: device.id,
          status: :failure,
          response_time_ms: nil,
          checked_at: day2
        })

      {:ok, _} =
        Monitoring.create_check(%{
          device_id: device.id,
          status: :success,
          response_time_ms: 80,
          checked_at: DateTime.add(day2, 3600, :second)
        })

      start_time = DateTime.add(day1, -24 * 60 * 60, :second)
      end_time = DateTime.add(day2, 24 * 60 * 60, :second)

      result = Monitoring.get_daily_stats(device.id, start_time, end_time)
      assert %Postgrex.Result{} = result
      assert length(result.rows) == 2

      # First day should have 2 successful checks
      [first_day | _] = result.rows
      [_bucket, total, successful, failed, avg_response, _min, _max, uptime] = first_day
      assert total == 2
      assert successful == 2
      assert failed == 0
      assert avg_response == Decimal.new("55.00")
      assert uptime == Decimal.new("100.00")
    end

    test "get_uptime_percentage/1 returns uptime percentage", %{device: device} do
      base_time = DateTime.utc_now()

      # Create 3 successful and 1 failed check
      {:ok, _} =
        Monitoring.create_check(%{
          device_id: device.id,
          status: :success,
          response_time_ms: 50,
          checked_at: DateTime.add(base_time, -60, :second)
        })

      {:ok, _} =
        Monitoring.create_check(%{
          device_id: device.id,
          status: :success,
          response_time_ms: 60,
          checked_at: DateTime.add(base_time, -120, :second)
        })

      {:ok, _} =
        Monitoring.create_check(%{
          device_id: device.id,
          status: :failure,
          response_time_ms: nil,
          checked_at: DateTime.add(base_time, -180, :second)
        })

      {:ok, _} =
        Monitoring.create_check(%{
          device_id: device.id,
          status: :success,
          response_time_ms: 70,
          checked_at: DateTime.add(base_time, -240, :second)
        })

      result = Monitoring.get_uptime_percentage(device.id)
      assert result == 75.0
    end

    test "get_uptime_percentage/2 accepts custom days parameter", %{device: device} do
      base_time = DateTime.utc_now()

      # Create checks within the last 7 days
      {:ok, _} =
        Monitoring.create_check(%{
          device_id: device.id,
          status: :success,
          response_time_ms: 50,
          checked_at: DateTime.add(base_time, -3 * 24 * 60 * 60, :second)
        })

      {:ok, _} =
        Monitoring.create_check(%{
          device_id: device.id,
          status: :success,
          response_time_ms: 60,
          checked_at: DateTime.add(base_time, -5 * 24 * 60 * 60, :second)
        })

      # Create a check older than 7 days (should not be included)
      {:ok, _} =
        Monitoring.create_check(%{
          device_id: device.id,
          status: :failure,
          response_time_ms: nil,
          checked_at: DateTime.add(base_time, -10 * 24 * 60 * 60, :second)
        })

      result = Monitoring.get_uptime_percentage(device.id, 7)
      assert result == 100.0
    end

    test "get_uptime_percentage/1 returns nil when no checks exist", %{device: device} do
      result = Monitoring.get_uptime_percentage(device.id)
      assert is_nil(result)
    end

    test "get_latency_data/2 returns latency checks for graphing", %{device: device} do
      base_time = ~U[2025-12-21 12:00:00Z]

      # Create successful checks with varying latency
      {:ok, _} =
        Monitoring.create_check(%{
          device_id: device.id,
          status: :success,
          response_time_ms: 50,
          checked_at: base_time
        })

      {:ok, _} =
        Monitoring.create_check(%{
          device_id: device.id,
          status: :success,
          response_time_ms: 75,
          checked_at: DateTime.add(base_time, 60, :second)
        })

      {:ok, _} =
        Monitoring.create_check(%{
          device_id: device.id,
          status: :success,
          response_time_ms: 100,
          checked_at: DateTime.add(base_time, 120, :second)
        })

      # Create a failed check (should be excluded)
      {:ok, _} =
        Monitoring.create_check(%{
          device_id: device.id,
          status: :failure,
          response_time_ms: nil,
          checked_at: DateTime.add(base_time, 180, :second)
        })

      since = DateTime.add(base_time, -3600, :second)
      result = Monitoring.get_latency_data(device.id, since: since, limit: 100)

      assert length(result) == 3
      # Results should be ordered by checked_at descending
      assert Enum.at(result, 0).response_time_ms == 100
      assert Enum.at(result, 1).response_time_ms == 75
      assert Enum.at(result, 2).response_time_ms == 50
    end

    test "get_latency_data/2 respects limit option", %{device: device} do
      base_time = ~U[2025-12-21 12:00:00Z]

      # Create 5 checks
      for i <- 1..5 do
        {:ok, _} =
          Monitoring.create_check(%{
            device_id: device.id,
            status: :success,
            response_time_ms: 50 + i,
            checked_at: DateTime.add(base_time, i * 60, :second)
          })
      end

      since = DateTime.add(base_time, -3600, :second)
      result = Monitoring.get_latency_data(device.id, since: since, limit: 3)

      assert length(result) == 3
    end

    test "get_latency_data/2 respects since option", %{device: device} do
      base_time = ~U[2025-12-21 12:00:00Z]

      # Create check before cutoff
      {:ok, _} =
        Monitoring.create_check(%{
          device_id: device.id,
          status: :success,
          response_time_ms: 50,
          checked_at: DateTime.add(base_time, -7200, :second)
        })

      # Create check after cutoff
      {:ok, _} =
        Monitoring.create_check(%{
          device_id: device.id,
          status: :success,
          response_time_ms: 75,
          checked_at: base_time
        })

      since = DateTime.add(base_time, -3600, :second)
      result = Monitoring.get_latency_data(device.id, since: since, limit: 100)

      # Should only include the check after the since timestamp
      assert length(result) == 1
      assert hd(result).response_time_ms == 75
    end

    test "get_latency_data/2 returns empty list when no checks exist", %{device: device} do
      since = DateTime.add(DateTime.utc_now(), -3600, :second)
      result = Monitoring.get_latency_data(device.id, since: since, limit: 100)

      assert result == []
    end

    test "get_latency_data/2 excludes failed checks", %{device: device} do
      base_time = ~U[2025-12-21 12:00:00Z]

      # Create only failed checks
      {:ok, _} =
        Monitoring.create_check(%{
          device_id: device.id,
          status: :failure,
          response_time_ms: nil,
          checked_at: base_time
        })

      {:ok, _} =
        Monitoring.create_check(%{
          device_id: device.id,
          status: :failure,
          response_time_ms: nil,
          checked_at: DateTime.add(base_time, 60, :second)
        })

      since = DateTime.add(base_time, -3600, :second)
      result = Monitoring.get_latency_data(device.id, since: since, limit: 100)

      assert result == []
    end
  end

  describe "ping" do
    alias Towerops.Monitoring.Ping

    @tag :integration
    test "ping/1 returns ok tuple with response time for localhost" do
      # Test with localhost which should always be reachable
      case Ping.ping("127.0.0.1", 2000) do
        {:ok, response_time} ->
          assert is_integer(response_time)
          assert response_time >= 0

        {:error, _reason} ->
          # Ping might fail in some test environments
          assert true
      end
    end

    @tag :integration
    test "ping/1 returns error tuple for unreachable host" do
      # Use a non-routable IP
      case Ping.ping("192.0.2.1", 1000) do
        {:error, _reason} -> assert true
        {:ok, _} -> assert true
      end
    end

    @tag :integration
    test "ping/1 with custom timeout" do
      result = Ping.ping("127.0.0.1", 5000)
      # Result should be either {:ok, time} or {:error, reason}
      assert is_tuple(result)
      assert elem(result, 0) in [:ok, :error]
    end
  end
end
