defmodule Berrypod.Analytics.BufferTest do
  use Berrypod.DataCase, async: false

  alias Berrypod.Analytics.Buffer
  alias Berrypod.Analytics.Event
  alias Berrypod.Repo

  import Ecto.Query

  setup do
    # Flush any pending events from previous tests
    send(Buffer, :flush)
    :timer.sleep(50)
    :ok
  end

  describe "record/1 and flush" do
    test "buffered events are flushed to the database" do
      visitor_hash = :crypto.strong_rand_bytes(8)

      Buffer.record(%{
        name: "pageview",
        pathname: "/buffer-test",
        visitor_hash: visitor_hash,
        browser: "Chrome",
        os: "macOS"
      })

      send(Buffer, :flush)
      :timer.sleep(50)

      events =
        from(e in Event, where: e.visitor_hash == ^visitor_hash)
        |> Repo.all()

      assert length(events) == 1

      event = hd(events)
      assert event.name == "pageview"
      assert event.pathname == "/buffer-test"
      assert event.visitor_hash == visitor_hash
      assert event.browser == "Chrome"
      assert event.os == "macOS"
      assert event.session_hash != nil
      assert byte_size(event.session_hash) == 8
    end

    test "events within 30 min get the same session_hash" do
      visitor_hash = :crypto.strong_rand_bytes(8)

      Buffer.record(%{name: "pageview", pathname: "/", visitor_hash: visitor_hash})
      Buffer.record(%{name: "pageview", pathname: "/products", visitor_hash: visitor_hash})

      send(Buffer, :flush)
      :timer.sleep(50)

      events =
        from(e in Event, where: e.visitor_hash == ^visitor_hash)
        |> Repo.all()

      assert length(events) == 2

      session_hashes = Enum.map(events, & &1.session_hash) |> Enum.uniq()
      assert length(session_hashes) == 1
    end

    test "different visitors get different session_hashes" do
      visitor1 = :crypto.strong_rand_bytes(8)
      visitor2 = :crypto.strong_rand_bytes(8)

      Buffer.record(%{name: "pageview", pathname: "/", visitor_hash: visitor1})
      Buffer.record(%{name: "pageview", pathname: "/", visitor_hash: visitor2})

      send(Buffer, :flush)
      :timer.sleep(50)

      events =
        from(e in Event, where: e.visitor_hash in [^visitor1, ^visitor2])
        |> Repo.all()

      assert length(events) == 2

      session_hashes = Enum.map(events, & &1.session_hash) |> Enum.uniq()
      assert length(session_hashes) == 2
    end
  end

  describe "supersede/1" do
    test "removes a buffered event by id before flush" do
      visitor_hash = :crypto.strong_rand_bytes(8)
      ref = Ecto.UUID.generate()

      Buffer.record(%{
        id: ref,
        name: "pageview",
        pathname: "/",
        visitor_hash: visitor_hash
      })

      Buffer.supersede(ref)

      send(Buffer, :flush)
      :timer.sleep(50)

      events =
        from(e in Event, where: e.visitor_hash == ^visitor_hash)
        |> Repo.all()

      assert events == []
    end

    test "only removes the event with the matching id" do
      visitor_hash = :crypto.strong_rand_bytes(8)
      ref = Ecto.UUID.generate()

      Buffer.record(%{
        id: ref,
        name: "pageview",
        pathname: "/",
        visitor_hash: visitor_hash
      })

      Buffer.record(%{
        name: "pageview",
        pathname: "/about",
        visitor_hash: visitor_hash
      })

      Buffer.supersede(ref)

      send(Buffer, :flush)
      :timer.sleep(50)

      events =
        from(e in Event, where: e.visitor_hash == ^visitor_hash)
        |> Repo.all()

      assert length(events) == 1
      assert hd(events).pathname == "/about"
    end

    test "removes from DB if event was already flushed" do
      visitor_hash = :crypto.strong_rand_bytes(8)
      ref = Ecto.UUID.generate()

      Buffer.record(%{
        id: ref,
        name: "pageview",
        pathname: "/",
        visitor_hash: visitor_hash
      })

      # Flush to DB first, simulating the 10s timer firing before supersede
      send(Buffer, :flush)
      :timer.sleep(50)

      assert Repo.aggregate(from(e in Event, where: e.id == ^ref), :count) == 1

      # Now supersede — should delete from DB
      Buffer.supersede(ref)
      :timer.sleep(50)

      assert Repo.aggregate(from(e in Event, where: e.id == ^ref), :count) == 0
    end

    test "no-op when ref does not match any buffered event" do
      visitor_hash = :crypto.strong_rand_bytes(8)

      Buffer.record(%{
        name: "pageview",
        pathname: "/",
        visitor_hash: visitor_hash
      })

      Buffer.supersede(Ecto.UUID.generate())

      send(Buffer, :flush)
      :timer.sleep(50)

      events =
        from(e in Event, where: e.visitor_hash == ^visitor_hash)
        |> Repo.all()

      assert length(events) == 1
    end
  end
end
