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

  import Towerops.DevicesFixtures

  alias Towerops.Devices.DeviceFirmwareHistory
  alias Towerops.Devices.Firmware
  alias Towerops.Snmp.Device, as: SnmpDevice

  describe "upsert_firmware_release/1" do
    test "creates a new firmware release" do
      attrs = %{
        vendor: "mikrotik",
        product_line: "routeros",
        version: "7.14.1",
        release_date: ~D[2025-01-15],
        download_url: "https://mikrotik.com/download",
        changelog_url: "https://mikrotik.com/download/changelogs",
        fetched_at: DateTime.utc_now(),
        metadata: %{"rss_title" => "RouterOS latest stable version"}
      }

      assert {:ok, release} = Firmware.upsert_firmware_release(attrs)
      assert release.vendor == "mikrotik"
      assert release.product_line == "routeros"
      assert release.version == "7.14.1"
      assert release.release_date == ~D[2025-01-15]
    end

    test "updates existing firmware release" do
      # Create initial release
      attrs = %{
        vendor: "mikrotik",
        product_line: "routeros",
        version: "7.14.0",
        fetched_at: ~U[2025-01-01 00:00:00Z]
      }

      assert {:ok, initial} = Firmware.upsert_firmware_release(attrs)
      initial_id = initial.id

      # Update with new version
      updated_attrs = %{
        vendor: "mikrotik",
        product_line: "routeros",
        version: "7.14.1",
        fetched_at: ~U[2025-01-15 00:00:00Z]
      }

      assert {:ok, updated} = Firmware.upsert_firmware_release(updated_attrs)
      assert updated.id == initial_id
      assert updated.version == "7.14.1"

      # Verify only one record exists
      assert [_release] = Firmware.list_firmware_releases()
    end

    test "returns error for invalid data" do
      attrs = %{vendor: "mikrotik"}

      assert {:error, changeset} = Firmware.upsert_firmware_release(attrs)
      refute changeset.valid?
    end
  end

  describe "get_latest_firmware_release/2" do
    test "returns firmware release when it exists" do
      attrs = %{
        vendor: "mikrotik",
        product_line: "routeros",
        version: "7.14.1",
        fetched_at: DateTime.utc_now()
      }

      {:ok, _release} = Firmware.upsert_firmware_release(attrs)

      assert release = Firmware.get_latest_firmware_release("mikrotik", "routeros")
      assert release.vendor == "mikrotik"
      assert release.product_line == "routeros"
      assert release.version == "7.14.1"
    end

    test "returns nil when firmware release does not exist" do
      assert is_nil(Firmware.get_latest_firmware_release("unknown", "unknown"))
    end

    test "returns nil when vendor is nil" do
      assert is_nil(Firmware.get_latest_firmware_release(nil, "routeros"))
    end

    test "returns nil when product_line is nil" do
      assert is_nil(Firmware.get_latest_firmware_release("mikrotik", nil))
    end

    test "returns nil when both vendor and product_line are nil" do
      assert is_nil(Firmware.get_latest_firmware_release(nil, nil))
    end
  end

  describe "list_firmware_releases/0" do
    test "returns all firmware releases" do
      {:ok, _r1} =
        Firmware.upsert_firmware_release(%{
          vendor: "mikrotik",
          product_line: "routeros",
          version: "7.14.1",
          fetched_at: DateTime.utc_now()
        })

      {:ok, _r2} =
        Firmware.upsert_firmware_release(%{
          vendor: "cisco",
          product_line: "ios-xe",
          version: "17.9.4a",
          fetched_at: DateTime.utc_now()
        })

      releases = Firmware.list_firmware_releases()
      assert length(releases) == 2
    end

    test "returns empty list when no releases exist" do
      assert Firmware.list_firmware_releases() == []
    end
  end

  describe "list_device_firmware_history/2" do
    test "returns empty list for device with no history" do
      snmp_device = insert_snmp_device()

      assert Firmware.list_device_firmware_history(snmp_device.id) == []
    end

    test "returns history in descending order by detected_at" do
      snmp_device = insert_snmp_device()
      now = DateTime.utc_now()
      earlier = DateTime.add(now, -3600, :second)

      insert_firmware_history(snmp_device.id, "7.12.0", "7.13.0", earlier)
      insert_firmware_history(snmp_device.id, "7.13.0", "7.14.1", now)

      history = Firmware.list_device_firmware_history(snmp_device.id)

      assert length(history) == 2
      assert Enum.at(history, 0).new_version == "7.14.1"
      assert Enum.at(history, 1).new_version == "7.13.0"
    end

    test "respects limit parameter" do
      snmp_device = insert_snmp_device()
      now = DateTime.utc_now()

      insert_firmware_history(snmp_device.id, "7.12.0", "7.13.0", DateTime.add(now, -7200, :second))
      insert_firmware_history(snmp_device.id, "7.13.0", "7.14.0", DateTime.add(now, -3600, :second))
      insert_firmware_history(snmp_device.id, "7.14.0", "7.14.1", now)

      history = Firmware.list_device_firmware_history(snmp_device.id, 2)

      assert length(history) == 2
    end
  end

  describe "log_firmware_change/3" do
    test "creates firmware history record" do
      snmp_device = insert_snmp_device()

      assert {:ok, history} = Firmware.log_firmware_change(snmp_device.id, "7.12.0", "7.14.1")
      assert history.snmp_device_id == snmp_device.id
      assert history.old_version == "7.12.0"
      assert history.new_version == "7.14.1"
      assert history.detection_method == "discovery"
      assert history.detected_at
    end

    test "broadcasts change via PubSub" do
      snmp_device = insert_snmp_device()

      Phoenix.PubSub.subscribe(Towerops.PubSub, "device:#{snmp_device.id}")

      {:ok, _history} = Firmware.log_firmware_change(snmp_device.id, "7.12.0", "7.14.1")

      assert_receive {:firmware_changed, device_id, "7.12.0", "7.14.1"}
      assert device_id == snmp_device.id
    end
  end

  defp insert_firmware_history(snmp_device_id, old_version, new_version, detected_at) do
    %DeviceFirmwareHistory{}
    |> DeviceFirmwareHistory.changeset(%{
      snmp_device_id: snmp_device_id,
      old_version: old_version,
      new_version: new_version,
      detected_at: detected_at,
      detection_method: "discovery"
    })
    |> Repo.insert!()
  end

  defp insert_snmp_device do
    device = device_fixture()

    %SnmpDevice{}
    |> SnmpDevice.changeset(%{
      device_id: device.id,
      sys_descr: "RouterOS v7.13.5",
      sys_object_id: "1.3.6.1.4.1.14988.1",
      sys_uptime: 0,
      sys_name: "test-router",
      sys_location: "Test Lab"
    })
    |> Repo.insert!()
  end
end
