defmodule Towerops.Workers.BackupTimeoutWorkerTest do
  use Towerops.DataCase, async: true

  import Towerops.AccountsFixtures

  alias Towerops.Devices
  alias Towerops.Devices.BackupRequests
  alias Towerops.Organizations
  alias Towerops.Sites
  alias Towerops.Workers.BackupTimeoutWorker

  describe "perform/1" do
    test "marks pending requests older than 5 minutes as timeout" do
      device = insert_device()

      # Create a pending request from 10 minutes ago (older than 5 minute timeout)
      {:ok, old_request} = BackupRequests.create_request(device.id, "job_old")

      old_request
      |> Ecto.Changeset.change(
        requested_at: DateTime.utc_now() |> DateTime.add(-600, :second) |> DateTime.truncate(:second)
      )
      |> Repo.update!()

      # Execute the worker
      assert :ok = BackupTimeoutWorker.perform(%Oban.Job{args: %{}})

      # Verify request was marked as timeout
      updated = Repo.reload(old_request)
      assert updated.status == "timeout"
      assert updated.error_message == "Request timed out"
      assert updated.completed_at
    end

    test "does not mark recent pending requests" do
      device = insert_device()

      # Create a pending request from 2 minutes ago (within 5 minute timeout)
      {:ok, recent_request} = BackupRequests.create_request(device.id, "job_recent")

      recent_request
      |> Ecto.Changeset.change(
        requested_at: DateTime.utc_now() |> DateTime.add(-120, :second) |> DateTime.truncate(:second)
      )
      |> Repo.update!()

      # Execute the worker
      assert :ok = BackupTimeoutWorker.perform(%Oban.Job{args: %{}})

      # Verify request still pending
      updated = Repo.reload(recent_request)
      assert updated.status == "pending"
      assert is_nil(updated.completed_at)
    end

    test "does not mark completed requests" do
      device = insert_device()

      # Create an old request that already succeeded
      {:ok, old_request} = BackupRequests.create_request(device.id, "job_old")

      old_request
      |> Ecto.Changeset.change(
        requested_at: DateTime.utc_now() |> DateTime.add(-600, :second) |> DateTime.truncate(:second)
      )
      |> Repo.update!()

      {:ok, _} = BackupRequests.update_request_status(old_request.id, "success", nil)

      # Execute the worker
      assert :ok = BackupTimeoutWorker.perform(%Oban.Job{args: %{}})

      # Verify request still has success status
      updated = Repo.reload(old_request)
      assert updated.status == "success"
    end

    test "handles multiple requests with mixed statuses" do
      device = insert_device()

      # Create old pending request (should timeout)
      {:ok, old_pending} = BackupRequests.create_request(device.id, "job_old_pending")

      old_pending
      |> Ecto.Changeset.change(
        requested_at: DateTime.utc_now() |> DateTime.add(-600, :second) |> DateTime.truncate(:second)
      )
      |> Repo.update!()

      # Create recent pending request (should stay pending)
      {:ok, recent_pending} = BackupRequests.create_request(device.id, "job_recent_pending")

      recent_pending
      |> Ecto.Changeset.change(
        requested_at: DateTime.utc_now() |> DateTime.add(-120, :second) |> DateTime.truncate(:second)
      )
      |> Repo.update!()

      # Create old completed request (should stay success)
      {:ok, old_success} = BackupRequests.create_request(device.id, "job_old_success")

      old_success
      |> Ecto.Changeset.change(
        requested_at: DateTime.utc_now() |> DateTime.add(-600, :second) |> DateTime.truncate(:second)
      )
      |> Repo.update!()

      {:ok, _} = BackupRequests.update_request_status(old_success.id, "success", nil)

      # Execute the worker
      assert :ok = BackupTimeoutWorker.perform(%Oban.Job{args: %{}})

      # Verify only old pending was marked as timeout
      assert Repo.reload(old_pending).status == "timeout"
      assert Repo.reload(recent_pending).status == "pending"
      assert Repo.reload(old_success).status == "success"
    end

    test "handles boundary case at exactly 5 minutes" do
      device = insert_device()

      # Create a request at exactly 5 minutes (300 seconds) ago
      {:ok, boundary_request} = BackupRequests.create_request(device.id, "job_boundary")

      boundary_request
      |> Ecto.Changeset.change(
        requested_at: DateTime.utc_now() |> DateTime.add(-300, :second) |> DateTime.truncate(:second)
      )
      |> Repo.update!()

      # Execute the worker
      assert :ok = BackupTimeoutWorker.perform(%Oban.Job{args: %{}})

      # The cutoff uses `< cutoff`, so exactly 5 minutes should NOT timeout
      updated = Repo.reload(boundary_request)
      assert updated.status == "pending"
    end

    test "returns :ok when no requests need timeout" do
      device = insert_device()

      # Create only recent request
      {:ok, recent_request} = BackupRequests.create_request(device.id, "job_recent")

      recent_request
      |> Ecto.Changeset.change(
        requested_at: DateTime.utc_now() |> DateTime.add(-60, :second) |> DateTime.truncate(:second)
      )
      |> Repo.update!()

      # Execute the worker
      assert :ok = BackupTimeoutWorker.perform(%Oban.Job{args: %{}})
    end

    test "returns :ok when no requests exist at all" do
      # Execute the worker with no requests in the database
      assert :ok = BackupTimeoutWorker.perform(%Oban.Job{args: %{}})
    end

    test "handles multiple devices with stale requests" do
      device1 = insert_device()
      device2 = insert_device()

      # Create old pending requests for both devices
      {:ok, request1} = BackupRequests.create_request(device1.id, "job1")

      request1
      |> Ecto.Changeset.change(
        requested_at: DateTime.utc_now() |> DateTime.add(-600, :second) |> DateTime.truncate(:second)
      )
      |> Repo.update!()

      {:ok, request2} = BackupRequests.create_request(device2.id, "job2")

      request2
      |> Ecto.Changeset.change(
        requested_at: DateTime.utc_now() |> DateTime.add(-700, :second) |> DateTime.truncate(:second)
      )
      |> Repo.update!()

      # Execute the worker
      assert :ok = BackupTimeoutWorker.perform(%Oban.Job{args: %{}})

      # Verify both were marked as timeout
      assert Repo.reload(request1).status == "timeout"
      assert Repo.reload(request2).status == "timeout"
    end
  end

  # Helper function to create a device
  defp insert_device 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})

    {:ok, device} =
      Devices.create_device(%{
        name: "Test Device #{System.unique_integer()}",
        ip_address: "192.168.1.#{:rand.uniform(254)}",
        site_id: site.id,
        organization_id: organization.id
      })

    device
  end
end
