defmodule Berrypod.Products.ProductTest do
  use Berrypod.DataCase, async: false

  alias Berrypod.Products.Product

  import Berrypod.ProductsFixtures

  describe "changeset/2" do
    setup do
      conn = provider_connection_fixture()
      {:ok, conn: conn}
    end

    test "valid attributes create a valid changeset", %{conn: conn} do
      attrs = valid_product_attrs(%{provider_connection_id: conn.id})
      changeset = Product.changeset(%Product{}, attrs)

      assert changeset.valid?
    end

    test "requires provider_connection_id", %{conn: _conn} do
      attrs = valid_product_attrs() |> Map.put(:provider_connection_id, nil)
      changeset = Product.changeset(%Product{}, attrs)

      refute changeset.valid?
      assert "can't be blank" in errors_on(changeset).provider_connection_id
    end

    test "requires provider_product_id", %{conn: conn} do
      attrs =
        valid_product_attrs(%{provider_connection_id: conn.id})
        |> Map.delete(:provider_product_id)

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

      refute changeset.valid?
      assert "can't be blank" in errors_on(changeset).provider_product_id
    end

    test "requires title", %{conn: conn} do
      attrs =
        valid_product_attrs(%{provider_connection_id: conn.id})
        |> Map.delete(:title)

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

      refute changeset.valid?
      assert "can't be blank" in errors_on(changeset).title
    end

    test "requires slug", %{conn: conn} do
      attrs =
        valid_product_attrs(%{provider_connection_id: conn.id})
        |> Map.delete(:slug)
        |> Map.delete(:title)

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

      refute changeset.valid?
      assert "can't be blank" in errors_on(changeset).slug
    end

    test "validates status is in allowed list", %{conn: conn} do
      attrs = valid_product_attrs(%{provider_connection_id: conn.id, status: "invalid"})
      changeset = Product.changeset(%Product{}, attrs)

      refute changeset.valid?
      assert "is invalid" in errors_on(changeset).status
    end

    test "accepts all valid statuses", %{conn: conn} do
      for status <- Product.statuses() do
        attrs = valid_product_attrs(%{provider_connection_id: conn.id, status: status})
        changeset = Product.changeset(%Product{}, attrs)
        assert changeset.valid?, "Expected #{status} to be valid"
      end
    end

    test "defaults status to active", %{conn: conn} do
      attrs =
        valid_product_attrs(%{provider_connection_id: conn.id})
        |> Map.delete(:status)

      changeset = Product.changeset(%Product{}, attrs)
      assert changeset.valid?
    end

    test "defaults visible to true", %{conn: conn} do
      attrs =
        valid_product_attrs(%{provider_connection_id: conn.id})
        |> Map.delete(:visible)

      changeset = Product.changeset(%Product{}, attrs)
      assert changeset.valid?
    end

    test "stores provider_data as map", %{conn: conn} do
      provider_data = %{"blueprint_id" => 145, "print_provider_id" => 29, "extra" => "value"}

      attrs =
        valid_product_attrs(%{provider_connection_id: conn.id, provider_data: provider_data})

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

      assert changeset.valid?
      assert changeset.changes.provider_data == provider_data
    end
  end

  describe "slug generation" do
    setup do
      conn = provider_connection_fixture()
      {:ok, conn: conn}
    end

    test "generates slug from title when slug not provided", %{conn: conn} do
      attrs =
        valid_product_attrs(%{
          provider_connection_id: conn.id,
          title: "My Awesome Product"
        })
        |> Map.delete(:slug)

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

      assert changeset.valid?
      assert changeset.changes.slug == "my-awesome-product"
    end

    test "uses provided slug over generated one", %{conn: conn} do
      attrs =
        valid_product_attrs(%{
          provider_connection_id: conn.id,
          title: "My Awesome Product",
          slug: "custom-slug"
        })

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

      assert changeset.valid?
      assert changeset.changes.slug == "custom-slug"
    end

    test "handles special characters in title for slug generation", %{conn: conn} do
      attrs =
        valid_product_attrs(%{
          provider_connection_id: conn.id,
          title: "Product (Special) & More!"
        })
        |> Map.delete(:slug)

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

      assert changeset.valid?
      # Special chars removed, spaces become dashes, consecutive dashes collapsed
      assert changeset.changes.slug == "product-special-more"
    end
  end

  describe "compute_checksum/1" do
    test "generates consistent checksum for same data" do
      data = %{"title" => "Test", "price" => 100}

      checksum1 = Product.compute_checksum(data)
      checksum2 = Product.compute_checksum(data)

      assert checksum1 == checksum2
    end

    test "generates different checksum for different data" do
      data1 = %{"title" => "Test", "price" => 100}
      data2 = %{"title" => "Test", "price" => 200}

      checksum1 = Product.compute_checksum(data1)
      checksum2 = Product.compute_checksum(data2)

      assert checksum1 != checksum2
    end

    test "returns 16-character hex string" do
      data = %{"key" => "value"}
      checksum = Product.compute_checksum(data)

      assert is_binary(checksum)
      assert String.length(checksum) == 16
      assert Regex.match?(~r/^[a-f0-9]+$/, checksum)
    end

    test "returns nil for non-map input" do
      assert Product.compute_checksum(nil) == nil
      assert Product.compute_checksum("string") == nil
      assert Product.compute_checksum(123) == nil
    end

    test "handles nested maps" do
      data = %{
        "title" => "Test",
        "options" => [
          %{"name" => "Size", "values" => ["S", "M", "L"]}
        ]
      }

      checksum = Product.compute_checksum(data)
      assert is_binary(checksum)
    end
  end

  describe "unique constraints" do
    test "enforces unique slug" do
      _product1 = product_fixture(%{slug: "unique-product"})

      conn = provider_connection_fixture(%{provider_type: "gelato"})

      assert {:error, changeset} =
               Berrypod.Products.create_product(
                 valid_product_attrs(%{
                   provider_connection_id: conn.id,
                   slug: "unique-product"
                 })
               )

      assert "has already been taken" in errors_on(changeset).slug
    end

    test "enforces unique provider_connection_id + provider_product_id" do
      conn = provider_connection_fixture()
      _product1 = product_fixture(%{provider_connection: conn, provider_product_id: "ext_123"})

      assert {:error, changeset} =
               Berrypod.Products.create_product(
                 valid_product_attrs(%{
                   provider_connection_id: conn.id,
                   provider_product_id: "ext_123"
                 })
               )

      assert "has already been taken" in errors_on(changeset).provider_connection_id
    end
  end

  describe "statuses/0" do
    test "returns list of valid statuses" do
      statuses = Product.statuses()

      assert is_list(statuses)
      assert "active" in statuses
      assert "draft" in statuses
      assert "archived" in statuses
    end
  end

  # =============================================================================
  # Display helpers
  # =============================================================================

  describe "primary_image/1" do
    test "returns image with lowest position" do
      product = %{images: [%{position: 2, src: "b.jpg"}, %{position: 0, src: "a.jpg"}]}
      assert Product.primary_image(product).src == "a.jpg"
    end

    test "returns nil with no images" do
      assert Product.primary_image(%{images: []}) == nil
    end

    test "returns nil when images not present" do
      assert Product.primary_image(%{}) == nil
    end
  end

  describe "hover_image/1" do
    test "returns second image by position" do
      product = %{images: [%{position: 0, src: "a.jpg"}, %{position: 1, src: "b.jpg"}]}
      assert Product.hover_image(product).src == "b.jpg"
    end

    test "returns nil with fewer than 2 images" do
      assert Product.hover_image(%{images: [%{position: 0, src: "a.jpg"}]}) == nil
    end

    test "returns nil with no images" do
      assert Product.hover_image(%{images: []}) == nil
    end
  end

  describe "option_types/1" do
    test "extracts from provider_data" do
      product = %{
        provider_data: %{
          "options" => [
            %{
              "name" => "Size",
              "type" => "size",
              "values" => [%{"title" => "S"}, %{"title" => "M"}]
            },
            %{
              "name" => "Color",
              "type" => "color",
              "values" => [%{"title" => "Red", "colors" => ["#FF0000"]}]
            }
          ]
        }
      }

      types = Product.option_types(product)
      assert length(types) == 2
      assert hd(types) == %{name: "Size", type: :size, values: [%{title: "S"}, %{title: "M"}]}

      color_type = Enum.at(types, 1)
      assert color_type.type == :color
      assert hd(color_type.values) == %{title: "Red", hex: "#FF0000"}
    end

    test "returns empty list when no provider_data" do
      assert Product.option_types(%{}) == []
    end

    test "falls back to option_types field on mock data" do
      mock = %{option_types: [%{name: "Size", values: ["S", "M"]}]}
      assert Product.option_types(mock) == [%{name: "Size", values: ["S", "M"]}]
    end
  end
end
