defmodule Berrypod.ActivityLogTest do
  use Berrypod.DataCase, async: false
  use Oban.Testing, repo: Berrypod.Repo

  alias Berrypod.ActivityLog
  alias Berrypod.ActivityLog.Entry

  import Berrypod.OrdersFixtures

  # ── log_event/3 ──

  describe "log_event/3" do
    test "creates an entry with correct fields" do
      {:ok, entry} = ActivityLog.log_event("sync.completed", "Product sync complete")

      assert entry.event_type == "sync.completed"
      assert entry.message == "Product sync complete"
      assert entry.level == "info"
      assert entry.occurred_at
    end

    test "accepts level, order_id, payload, and occurred_at options" do
      order = order_fixture(%{payment_status: "paid"})
      ts = ~U[2026-02-15 10:00:00Z]

      {:ok, entry} =
        ActivityLog.log_event("order.submitted", "Submitted to Printify",
          level: "warning",
          order_id: order.id,
          payload: %{provider_order_id: "PFY-123"},
          occurred_at: ts
        )

      assert entry.level == "warning"
      assert entry.order_id == order.id
      assert entry.payload == %{provider_order_id: "PFY-123"}
      assert entry.occurred_at == ts
    end

    test "defaults level to info and occurred_at to now" do
      {:ok, entry} = ActivityLog.log_event("sync.started", "Starting sync")

      assert entry.level == "info"
      assert DateTime.diff(DateTime.utc_now(), entry.occurred_at, :second) < 2
    end

    test "never raises on invalid attrs" do
      # missing required message (nil)
      result = ActivityLog.log_event("test.event", nil)
      assert result == :ok
    end

    test "broadcasts on activity:all topic" do
      ActivityLog.subscribe()

      {:ok, entry} = ActivityLog.log_event("sync.started", "Starting sync")

      assert_receive {:new_activity, ^entry}
    end

    test "broadcasts on activity:order:<id> when order_id present" do
      order = order_fixture(%{payment_status: "paid"})
      ActivityLog.subscribe(order.id)

      {:ok, entry} =
        ActivityLog.log_event("order.submitted", "Submitted", order_id: order.id)

      assert_receive {:new_activity, ^entry}
    end

    test "does not broadcast on order topic when no order_id" do
      ActivityLog.subscribe()

      {:ok, _entry} = ActivityLog.log_event("sync.started", "Starting sync")

      assert_receive {:new_activity, _}
      # only the global topic message, no order-specific one
    end
  end

  # ── list_for_order/1 ──

  describe "list_for_order/1" do
    test "returns entries for a specific order sorted ASC by occurred_at" do
      order = order_fixture(%{payment_status: "paid"})

      {:ok, _} =
        ActivityLog.log_event("order.created", "Order placed",
          order_id: order.id,
          occurred_at: ~U[2026-02-15 10:00:00Z]
        )

      {:ok, _} =
        ActivityLog.log_event("order.submitted", "Submitted",
          order_id: order.id,
          occurred_at: ~U[2026-02-15 10:01:00Z]
        )

      {:ok, _} =
        ActivityLog.log_event("order.shipped", "Shipped",
          order_id: order.id,
          occurred_at: ~U[2026-02-15 12:00:00Z]
        )

      entries = ActivityLog.list_for_order(order.id)

      assert length(entries) == 3
      assert Enum.map(entries, & &1.event_type) == ~w(order.created order.submitted order.shipped)
    end

    test "returns empty list for order with no events" do
      assert ActivityLog.list_for_order(Ecto.UUID.generate()) == []
    end

    test "does not include events from other orders" do
      order1 = order_fixture(%{payment_status: "paid"})
      order2 = order_fixture(%{payment_status: "paid"})

      {:ok, _} = ActivityLog.log_event("order.created", "O1", order_id: order1.id)
      {:ok, _} = ActivityLog.log_event("order.created", "O2", order_id: order2.id)

      assert length(ActivityLog.list_for_order(order1.id)) == 1
      assert length(ActivityLog.list_for_order(order2.id)) == 1
    end
  end

  # ── list_recent/1 ──

  describe "list_recent/1" do
    test "returns paginated entries newest first" do
      for i <- 1..3 do
        {:ok, _} =
          ActivityLog.log_event("sync.completed", "Sync #{i}",
            occurred_at: DateTime.add(~U[2026-02-15 10:00:00Z], i, :minute)
          )
      end

      page = ActivityLog.list_recent()

      assert page.total_count == 3
      messages = Enum.map(page.items, & &1.message)
      assert List.first(messages) == "Sync 3"
      assert List.last(messages) == "Sync 1"
    end

    test "filters by attention tab" do
      {:ok, _} = ActivityLog.log_event("sync.completed", "OK")
      {:ok, _} = ActivityLog.log_event("sync.failed", "Failed", level: "error")

      page = ActivityLog.list_recent(tab: "attention")
      assert page.total_count == 1
      assert hd(page.items).level == "error"
    end

    test "filters by orders category" do
      {:ok, _} = ActivityLog.log_event("order.created", "Order placed")
      {:ok, _} = ActivityLog.log_event("sync.completed", "Sync done")

      page = ActivityLog.list_recent(category: "orders")
      assert page.total_count == 1
      assert hd(page.items).event_type == "order.created"
    end

    test "filters by syncs category" do
      {:ok, _} = ActivityLog.log_event("order.created", "Order placed")
      {:ok, _} = ActivityLog.log_event("sync.completed", "Sync done")

      page = ActivityLog.list_recent(category: "syncs")
      assert page.total_count == 1
      assert hd(page.items).event_type == "sync.completed"
    end

    test "filters by emails category" do
      {:ok, _} = ActivityLog.log_event("order.email.confirmation_sent", "Email sent")
      {:ok, _} = ActivityLog.log_event("sync.completed", "Sync done")

      page = ActivityLog.list_recent(category: "emails")
      assert page.total_count == 1
      assert hd(page.items).event_type == "order.email.confirmation_sent"
    end

    test "filters by carts category" do
      {:ok, _} = ActivityLog.log_event("abandoned_cart.created", "Cart abandoned")
      {:ok, _} = ActivityLog.log_event("sync.completed", "Sync done")

      page = ActivityLog.list_recent(category: "carts")
      assert page.total_count == 1
      assert hd(page.items).event_type == "abandoned_cart.created"
    end

    test "searches by order number" do
      order = order_fixture(%{payment_status: "paid"})

      {:ok, _} = ActivityLog.log_event("order.created", "Order placed", order_id: order.id)
      {:ok, _} = ActivityLog.log_event("sync.completed", "Sync done")

      # Extract enough of the order number to match
      search = String.slice(order.order_number, 3, 6)
      page = ActivityLog.list_recent(search: search)

      assert page.total_count == 1
      assert hd(page.items).order_id == order.id
    end
  end

  # ── count_needing_attention/0 ──

  describe "count_needing_attention/0" do
    test "counts unresolved warnings and errors" do
      {:ok, _} = ActivityLog.log_event("sync.failed", "Error", level: "error")
      {:ok, _} = ActivityLog.log_event("order.cancelled", "Cancelled", level: "warning")
      {:ok, _} = ActivityLog.log_event("sync.completed", "OK")

      assert ActivityLog.count_needing_attention() == 2
    end

    test "returns 0 when all resolved" do
      {:ok, entry} = ActivityLog.log_event("sync.failed", "Error", level: "error")
      ActivityLog.resolve(entry.id)

      assert ActivityLog.count_needing_attention() == 0
    end
  end

  # ── resolve/1 ──

  describe "resolve/1" do
    test "sets resolved_at on an entry" do
      {:ok, entry} = ActivityLog.log_event("sync.failed", "Error", level: "error")
      assert is_nil(entry.resolved_at)

      {1, _} = ActivityLog.resolve(entry.id)

      updated = Repo.get!(Entry, entry.id)
      refute is_nil(updated.resolved_at)
    end
  end

  # ── resolve_all_for_order/1 ──

  describe "resolve_all_for_order/1" do
    test "resolves all unresolved entries for an order" do
      order = order_fixture(%{payment_status: "paid"})

      {:ok, _} =
        ActivityLog.log_event("order.submission_failed", "Failed",
          level: "error",
          order_id: order.id
        )

      {:ok, _} =
        ActivityLog.log_event("order.cancelled", "Cancelled",
          level: "warning",
          order_id: order.id
        )

      {2, _} = ActivityLog.resolve_all_for_order(order.id)

      entries = ActivityLog.list_for_order(order.id)
      assert Enum.all?(entries, fn e -> not is_nil(e.resolved_at) end)
    end
  end

  # ── PruneWorker ──

  describe "PruneWorker" do
    test "deletes entries older than 90 days" do
      old_time = DateTime.utc_now() |> DateTime.add(-91, :day) |> DateTime.truncate(:second)
      recent_time = DateTime.utc_now() |> DateTime.add(-1, :day) |> DateTime.truncate(:second)

      # Insert old entry directly to control inserted_at
      Repo.insert!(%Entry{
        event_type: "sync.completed",
        message: "Old sync",
        level: "info",
        occurred_at: old_time,
        inserted_at: old_time,
        updated_at: old_time
      })

      Repo.insert!(%Entry{
        event_type: "sync.completed",
        message: "Recent sync",
        level: "info",
        occurred_at: recent_time,
        inserted_at: recent_time,
        updated_at: recent_time
      })

      assert Repo.aggregate(Entry, :count) == 2

      :ok = perform_job(Berrypod.ActivityLog.PruneWorker, %{})

      entries = Repo.all(Entry)
      assert length(entries) == 1
      assert hd(entries).message == "Recent sync"
    end
  end
end
