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

  alias Towerops.Devices.FirmwareRelease

  describe "changeset/2" do
    test "valid changeset with all required fields" do
      attrs = %{
        vendor: "mikrotik",
        version: "7.14.1",
        fetched_at: DateTime.utc_now()
      }

      changeset = FirmwareRelease.changeset(%FirmwareRelease{}, attrs)

      assert changeset.valid?
    end

    test "valid changeset with optional product_line" do
      attrs = %{
        vendor: "cisco",
        product_line: "ios-xe",
        version: "17.9.4a",
        fetched_at: DateTime.utc_now()
      }

      changeset = FirmwareRelease.changeset(%FirmwareRelease{}, attrs)

      assert changeset.valid?
    end

    test "valid changeset with all optional fields" do
      attrs = %{
        vendor: "ubiquiti",
        product_line: "unifi",
        version: "7.3.83",
        release_date: ~D[2024-01-15],
        download_url: "https://example.com/download",
        changelog_url: "https://example.com/changelog",
        metadata: %{"rss_title" => "UniFi 7.3.83"},
        fetched_at: DateTime.utc_now()
      }

      changeset = FirmwareRelease.changeset(%FirmwareRelease{}, attrs)

      assert changeset.valid?
    end

    test "requires vendor" do
      attrs = %{
        version: "7.14.1",
        fetched_at: DateTime.utc_now()
      }

      changeset = FirmwareRelease.changeset(%FirmwareRelease{}, attrs)

      refute changeset.valid?
      assert %{vendor: ["can't be blank"]} = errors_on(changeset)
    end

    test "requires version" do
      attrs = %{
        vendor: "mikrotik",
        fetched_at: DateTime.utc_now()
      }

      changeset = FirmwareRelease.changeset(%FirmwareRelease{}, attrs)

      refute changeset.valid?
      assert %{version: ["can't be blank"]} = errors_on(changeset)
    end

    test "requires fetched_at" do
      attrs = %{
        vendor: "mikrotik",
        version: "7.14.1"
      }

      changeset = FirmwareRelease.changeset(%FirmwareRelease{}, attrs)

      refute changeset.valid?
      assert %{fetched_at: ["can't be blank"]} = errors_on(changeset)
    end

    test "allows nil product_line" do
      attrs = %{
        vendor: "mikrotik",
        product_line: nil,
        version: "7.14.1",
        fetched_at: DateTime.utc_now()
      }

      changeset = FirmwareRelease.changeset(%FirmwareRelease{}, attrs)

      assert changeset.valid?
    end

    test "defaults metadata to empty map" do
      attrs = %{
        vendor: "mikrotik",
        version: "7.14.1",
        fetched_at: DateTime.utc_now()
      }

      changeset = FirmwareRelease.changeset(%FirmwareRelease{}, attrs)

      assert changeset.valid?
      # Metadata should have default value from schema
      assert get_change(changeset, :metadata, %{}) == %{}
    end
  end

  describe "database constraints" do
    test "enforces unique constraint on vendor and product_line" do
      fetched_at = DateTime.utc_now()

      # Insert first record
      {:ok, _release} =
        %FirmwareRelease{}
        |> FirmwareRelease.changeset(%{
          vendor: "cisco",
          product_line: "ios-xe",
          version: "17.9.4a",
          fetched_at: fetched_at
        })
        |> Repo.insert()

      # Try to insert duplicate vendor/product_line combo
      result =
        %FirmwareRelease{}
        |> FirmwareRelease.changeset(%{
          vendor: "cisco",
          product_line: "ios-xe",
          version: "17.10.1",
          fetched_at: fetched_at
        })
        |> Repo.insert()

      assert {:error, changeset} = result
      assert %{vendor: ["has already been taken"]} = errors_on(changeset)
    end

    test "allows same vendor with different product_line" do
      fetched_at = DateTime.utc_now()

      {:ok, _release1} =
        %FirmwareRelease{}
        |> FirmwareRelease.changeset(%{
          vendor: "cisco",
          product_line: "ios-xe",
          version: "17.9.4a",
          fetched_at: fetched_at
        })
        |> Repo.insert()

      {:ok, _release2} =
        %FirmwareRelease{}
        |> FirmwareRelease.changeset(%{
          vendor: "cisco",
          product_line: "ios-xr",
          version: "7.7.1",
          fetched_at: fetched_at
        })
        |> Repo.insert()

      # Should succeed - different product_line
      assert Repo.aggregate(FirmwareRelease, :count) == 2
    end

    test "allows same vendor with nil product_line separately from product_line values" do
      fetched_at = DateTime.utc_now()

      {:ok, _release1} =
        %FirmwareRelease{}
        |> FirmwareRelease.changeset(%{
          vendor: "mikrotik",
          product_line: nil,
          version: "7.14.1",
          fetched_at: fetched_at
        })
        |> Repo.insert()

      {:ok, _release2} =
        %FirmwareRelease{}
        |> FirmwareRelease.changeset(%{
          vendor: "mikrotik",
          product_line: "routeros",
          version: "7.14.1",
          fetched_at: fetched_at
        })
        |> Repo.insert()

      # Should succeed - nil product_line is distinct from "routeros"
      assert Repo.aggregate(FirmwareRelease, :count) == 2
    end
  end
end
