defmodule Berrypod.ReviewsTest do
  use Berrypod.DataCase, async: true

  alias Berrypod.Reviews
  alias Berrypod.Reviews.Review

  import Berrypod.ProductsFixtures
  import Berrypod.OrdersFixtures

  # ── Create ─────────────────────────────────────────────────────────

  describe "create_review/1" do
    test "creates a review with valid attrs" do
      product = product_fixture()

      attrs = %{
        product_id: product.id,
        email: "buyer@example.com",
        author_name: "Jane",
        rating: 5
      }

      assert {:ok, %Review{} = review} = Reviews.create_review(attrs)
      assert review.product_id == product.id
      assert review.email == "buyer@example.com"
      assert review.author_name == "Jane"
      assert review.rating == 5
      assert review.status == "pending"
    end

    test "normalises email to lowercase" do
      product = product_fixture()

      attrs = %{
        product_id: product.id,
        email: "BUYER@EXAMPLE.COM",
        author_name: "Jane",
        rating: 5
      }

      {:ok, review} = Reviews.create_review(attrs)
      assert review.email == "buyer@example.com"
    end

    test "auto-links to matching order for verified purchase badge" do
      conn = provider_connection_fixture(%{provider_type: "printify"})
      product = product_fixture(%{provider_connection: conn})
      variant = product_variant_fixture(%{product: product})

      order =
        order_fixture(%{
          customer_email: "buyer@example.com",
          payment_status: "paid",
          variant_id: variant.id
        })

      attrs = %{
        product_id: product.id,
        email: "buyer@example.com",
        author_name: "Jane",
        rating: 5
      }

      {:ok, review} = Reviews.create_review(attrs)
      assert review.order_id == order.id
    end

    test "allows optional title and body" do
      product = product_fixture()

      attrs = %{
        product_id: product.id,
        email: "buyer@example.com",
        author_name: "Jane",
        rating: 4,
        title: "Great product!",
        body: "Would buy again."
      }

      {:ok, review} = Reviews.create_review(attrs)
      assert review.title == "Great product!"
      assert review.body == "Would buy again."
    end

    test "validates rating is 1-5" do
      product = product_fixture()

      attrs = %{
        product_id: product.id,
        email: "buyer@example.com",
        author_name: "Jane",
        rating: 6
      }

      assert {:error, changeset} = Reviews.create_review(attrs)
      assert "is invalid" in errors_on(changeset).rating
    end

    test "enforces one review per email+product" do
      product = product_fixture()

      attrs = %{
        product_id: product.id,
        email: "buyer@example.com",
        author_name: "Jane",
        rating: 5
      }

      {:ok, _} = Reviews.create_review(attrs)
      {:error, changeset} = Reviews.create_review(attrs)
      assert "you have already reviewed this product" in errors_on(changeset).email
    end
  end

  # ── Read ───────────────────────────────────────────────────────────

  describe "get_review/1" do
    test "returns the review" do
      product = product_fixture()

      {:ok, review} =
        Reviews.create_review(%{
          product_id: product.id,
          email: "buyer@example.com",
          author_name: "Jane",
          rating: 5
        })

      assert Reviews.get_review(review.id) == review
    end

    test "returns nil for unknown id" do
      assert Reviews.get_review(Ecto.UUID.generate()) == nil
    end
  end

  describe "get_review_by_email_and_product/2" do
    test "returns the review for the email+product" do
      product = product_fixture()

      {:ok, review} =
        Reviews.create_review(%{
          product_id: product.id,
          email: "buyer@example.com",
          author_name: "Jane",
          rating: 5
        })

      assert Reviews.get_review_by_email_and_product("buyer@example.com", product.id) == review
    end

    test "returns nil when no review exists" do
      product = product_fixture()
      assert Reviews.get_review_by_email_and_product("nobody@example.com", product.id) == nil
    end
  end

  describe "list_reviews_for_product/2" do
    test "lists only approved reviews by default" do
      product = product_fixture()

      {:ok, pending} =
        Reviews.create_review(%{
          product_id: product.id,
          email: "pending@example.com",
          author_name: "Pending",
          rating: 3
        })

      {:ok, approved} =
        Reviews.create_review(%{
          product_id: product.id,
          email: "approved@example.com",
          author_name: "Approved",
          rating: 5
        })

      Reviews.approve_review(approved)

      reviews = Reviews.list_reviews_for_product(product.id)
      assert length(reviews) == 1
      assert hd(reviews).email == "approved@example.com"
      refute Enum.any?(reviews, &(&1.id == pending.id))
    end

    test "can filter by status" do
      product = product_fixture()

      {:ok, _} =
        Reviews.create_review(%{
          product_id: product.id,
          email: "pending@example.com",
          author_name: "Pending",
          rating: 3
        })

      reviews = Reviews.list_reviews_for_product(product.id, status: "pending")
      assert length(reviews) == 1
    end
  end

  describe "list_pending_reviews/0" do
    test "lists pending reviews with preloaded product" do
      product = product_fixture()

      {:ok, _} =
        Reviews.create_review(%{
          product_id: product.id,
          email: "buyer@example.com",
          author_name: "Jane",
          rating: 5
        })

      reviews = Reviews.list_pending_reviews()
      assert length(reviews) == 1
      assert hd(reviews).product.id == product.id
    end
  end

  # ── Update ─────────────────────────────────────────────────────────

  describe "update_review/2" do
    test "updates the review and resets status to pending" do
      product = product_fixture()

      {:ok, review} =
        Reviews.create_review(%{
          product_id: product.id,
          email: "buyer@example.com",
          author_name: "Jane",
          rating: 5
        })

      Reviews.approve_review(review)

      {:ok, updated} = Reviews.update_review(review, %{rating: 4, body: "Updated!"})
      assert updated.rating == 4
      assert updated.body == "Updated!"
      assert updated.status == "pending"
    end
  end

  # ── Moderation ─────────────────────────────────────────────────────

  describe "approve_review/1" do
    test "sets status to approved" do
      product = product_fixture()

      {:ok, review} =
        Reviews.create_review(%{
          product_id: product.id,
          email: "buyer@example.com",
          author_name: "Jane",
          rating: 5
        })

      {:ok, approved} = Reviews.approve_review(review)
      assert approved.status == "approved"
    end
  end

  describe "reject_review/1" do
    test "sets status to rejected" do
      product = product_fixture()

      {:ok, review} =
        Reviews.create_review(%{
          product_id: product.id,
          email: "buyer@example.com",
          author_name: "Jane",
          rating: 5
        })

      {:ok, rejected} = Reviews.reject_review(review)
      assert rejected.status == "rejected"
    end
  end

  # ── Aggregates ─────────────────────────────────────────────────────

  describe "average_rating_for_product/1" do
    test "returns average and count for approved reviews" do
      product = product_fixture()

      {:ok, r1} =
        Reviews.create_review(%{
          product_id: product.id,
          email: "a@example.com",
          author_name: "A",
          rating: 5
        })

      {:ok, r2} =
        Reviews.create_review(%{
          product_id: product.id,
          email: "b@example.com",
          author_name: "B",
          rating: 4
        })

      Reviews.approve_review(r1)
      Reviews.approve_review(r2)

      {avg, count} = Reviews.average_rating_for_product(product.id)
      assert Decimal.equal?(avg, Decimal.new("4.5"))
      assert count == 2
    end

    test "returns {nil, 0} when no approved reviews" do
      product = product_fixture()
      assert {nil, 0} = Reviews.average_rating_for_product(product.id)
    end

    test "excludes pending and rejected reviews" do
      product = product_fixture()

      {:ok, _} =
        Reviews.create_review(%{
          product_id: product.id,
          email: "pending@example.com",
          author_name: "Pending",
          rating: 1
        })

      assert {nil, 0} = Reviews.average_rating_for_product(product.id)
    end
  end

  # ── Purchase verification ──────────────────────────────────────────

  describe "can_review?/2" do
    test "returns true when email has purchased the product" do
      conn = provider_connection_fixture(%{provider_type: "printify"})
      product = product_fixture(%{provider_connection: conn})
      variant = product_variant_fixture(%{product: product})

      order_fixture(%{
        customer_email: "buyer@example.com",
        payment_status: "paid",
        variant_id: variant.id
      })

      assert Reviews.can_review?("buyer@example.com", product.id)
    end

    test "returns false when email has not purchased the product" do
      product = product_fixture()
      refute Reviews.can_review?("nobody@example.com", product.id)
    end

    test "returns false when order exists but for different product" do
      conn = provider_connection_fixture(%{provider_type: "printify"})
      product1 = product_fixture(%{provider_connection: conn})
      product2 = product_fixture(%{provider_connection: conn})
      variant1 = product_variant_fixture(%{product: product1})

      order_fixture(%{
        customer_email: "buyer@example.com",
        payment_status: "paid",
        variant_id: variant1.id
      })

      refute Reviews.can_review?("buyer@example.com", product2.id)
    end
  end

  # ── Delete ─────────────────────────────────────────────────────────

  describe "delete_review/1" do
    test "deletes the review" do
      product = product_fixture()

      {:ok, review} =
        Reviews.create_review(%{
          product_id: product.id,
          email: "buyer@example.com",
          author_name: "Jane",
          rating: 5
        })

      {:ok, _} = Reviews.delete_review(review)
      assert Reviews.get_review(review.id) == nil
    end
  end

  # ── Review tokens ────────────────────────────────────────────────────

  describe "generate_review_token/2 and verify_review_token/1" do
    test "generates and verifies a valid token" do
      product = product_fixture()
      email = "buyer@example.com"

      token = Reviews.generate_review_token(email, product.id)
      assert is_binary(token)

      {:ok, result} = Reviews.verify_review_token(token)
      assert result.email == email
      assert result.product_id == product.id
    end

    test "normalises email to lowercase" do
      product = product_fixture()
      token = Reviews.generate_review_token("BUYER@EXAMPLE.COM", product.id)

      {:ok, result} = Reviews.verify_review_token(token)
      assert result.email == "buyer@example.com"
    end

    test "rejects invalid tokens" do
      assert {:error, :invalid} = Reviews.verify_review_token("invalid_token")
    end
  end

  describe "request_review_verification/3" do
    test "returns error when email has not purchased product" do
      product = product_fixture()

      result =
        Reviews.request_review_verification("nobody@example.com", product.id, product.title)

      assert {:error, :no_purchase} = result
    end

    test "returns error when user has already reviewed" do
      product = product_fixture()

      {:ok, _} =
        Reviews.create_review(%{
          product_id: product.id,
          email: "buyer@example.com",
          author_name: "Jane",
          rating: 5
        })

      result = Reviews.request_review_verification("buyer@example.com", product.id, product.title)
      assert {:error, :already_reviewed} = result
    end

    test "sends verification email when user has purchased" do
      conn = provider_connection_fixture(%{provider_type: "printify"})
      product = product_fixture(%{provider_connection: conn})
      variant = product_variant_fixture(%{product: product})

      order_fixture(%{
        customer_email: "buyer@example.com",
        payment_status: "paid",
        variant_id: variant.id
      })

      result = Reviews.request_review_verification("buyer@example.com", product.id, product.title)
      assert {:ok, :sent} = result
    end
  end
end
