defmodule Towerops.Devices.BackupRequestsTest do
  use Towerops.DataCase, async: true

  import Towerops.AccountsFixtures

  alias Towerops.Devices
  alias Towerops.Devices.BackupRequests
  alias Towerops.Organizations
  alias Towerops.Sites

  describe "create_request/2" do
    test "creates backup request with pending status" do
      device = insert(:device)
      job_id = "backup:#{device.id}:#{DateTime.to_unix(DateTime.utc_now())}"

      assert {:ok, request} = BackupRequests.create_request(device.id, job_id)

      assert request.device_id == device.id
      assert request.job_id == job_id
      assert request.status == "pending"
      assert request.requested_at
      assert is_nil(request.completed_at)
      assert is_nil(request.error_message)
    end
  end

  describe "get_request_by_job_id/1" do
    test "returns request by job_id" do
      device = insert(:device)
      job_id = "backup:#{device.id}:12345"
      {:ok, request} = BackupRequests.create_request(device.id, job_id)

      fetched = BackupRequests.get_request_by_job_id(job_id)

      assert fetched.id == request.id
    end

    test "returns nil when job_id not found" do
      assert BackupRequests.get_request_by_job_id("nonexistent") == nil
    end
  end

  describe "update_request_status/3" do
    test "updates request to success status" do
      device = insert(:device)
      {:ok, request} = BackupRequests.create_request(device.id, "job123")

      assert {:ok, updated} = BackupRequests.update_request_status(request.id, "success", nil)

      assert updated.status == "success"
      assert updated.completed_at
      assert is_nil(updated.error_message)
    end

    test "updates request to failed status with error message" do
      device = insert(:device)
      {:ok, request} = BackupRequests.create_request(device.id, "job123")

      assert {:ok, updated} =
               BackupRequests.update_request_status(request.id, "failed", "Connection timeout")

      assert updated.status == "failed"
      assert updated.completed_at
      assert updated.error_message == "Connection timeout"
    end

    test "updates request to timeout status" do
      device = insert(:device)
      {:ok, request} = BackupRequests.create_request(device.id, "job123")

      assert {:ok, updated} =
               BackupRequests.update_request_status(request.id, "timeout", "Request timed out")

      assert updated.status == "timeout"
      assert updated.completed_at
      assert updated.error_message == "Request timed out"
    end
  end

  describe "mark_timed_out_requests/1" do
    test "marks old pending requests as timeout" do
      device = insert(:device)

      # Create request from 10 minutes ago
      {: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!()

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

      # Mark requests older than 5 minutes as timeout
      cutoff = DateTime.add(DateTime.utc_now(), -300, :second)
      count = BackupRequests.mark_timed_out_requests(cutoff)

      assert count == 1

      # Verify old request is marked timeout
      updated = BackupRequests.get_request_by_job_id("job_old")
      assert updated.status == "timeout"
      assert updated.completed_at
      assert updated.error_message == "Request timed out"
    end

    test "does not mark completed requests as timeout" do
      device = insert(:device)

      # Create 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!()

      BackupRequests.update_request_status(old_request.id, "success", nil)

      # Mark old pending requests
      cutoff = DateTime.add(DateTime.utc_now(), -300, :second)
      count = BackupRequests.mark_timed_out_requests(cutoff)

      assert count == 0
    end
  end

  describe "summary_since/1" do
    test "returns summary of backup request statuses" do
      device = insert(:device)

      # Create various requests
      Enum.each(1..5, fn i ->
        {:ok, req} = BackupRequests.create_request(device.id, "success_#{i}")
        BackupRequests.update_request_status(req.id, "success", nil)
      end)

      Enum.each(1..2, fn i ->
        {:ok, req} = BackupRequests.create_request(device.id, "failed_#{i}")
        BackupRequests.update_request_status(req.id, "failed", "Error")
      end)

      {:ok, req} = BackupRequests.create_request(device.id, "timeout_1")
      BackupRequests.update_request_status(req.id, "timeout", "Timed out")

      {:ok, _pending} = BackupRequests.create_request(device.id, "pending_1")

      # Get summary from beginning of time
      since = DateTime.add(DateTime.utc_now(), -86_400, :second)
      summary = BackupRequests.summary_since(since)

      assert summary.success == 5
      assert summary.failed == 2
      assert summary.timeout == 1
      assert summary.pending == 1
    end
  end

  describe "list_failed_devices_since/1" do
    test "returns devices with failed or timeout requests" do
      device1 = insert(:device)
      device2 = insert(:device)
      device3 = insert(:device)

      # device1: failed
      {:ok, req1} = BackupRequests.create_request(device1.id, "job1")
      BackupRequests.update_request_status(req1.id, "failed", "SNMP error")

      # device2: timeout
      {:ok, req2} = BackupRequests.create_request(device2.id, "job2")
      BackupRequests.update_request_status(req2.id, "timeout", "No response")

      # device3: success (should not appear)
      {:ok, req3} = BackupRequests.create_request(device3.id, "job3")
      BackupRequests.update_request_status(req3.id, "success", nil)

      since = DateTime.add(DateTime.utc_now(), -86_400, :second)
      failed_devices = BackupRequests.list_failed_devices_since(since)

      assert length(failed_devices) == 2

      device_ids = Enum.map(failed_devices, & &1.device_id)
      assert device1.id in device_ids
      assert device2.id in device_ids
      refute device3.id in device_ids
    end

    test "includes error messages and status" do
      device = insert(:device)

      {:ok, req} = BackupRequests.create_request(device.id, "job_fail")
      BackupRequests.update_request_status(req.id, "failed", "Connection refused")

      since = DateTime.add(DateTime.utc_now(), -86_400, :second)
      [result] = BackupRequests.list_failed_devices_since(since)

      assert result.device_id == device.id
      assert result.status == "failed"
      assert result.error_message == "Connection refused"
    end
  end

  # Helper functions

  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",
          ip_address: "192.168.1.1",
          site_id: site.id,
          organization_id: organization.id,
          monitoring_enabled: false,
          snmp_enabled: false
        },
        bypass_limits: true
      )

    device
  end
end
