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

  alias Berrypod.Redirects
  alias Berrypod.Redirects.Redirect

  setup do
    Redirects.create_table()
    :ok
  end

  describe "create_auto/1" do
    test "creates a redirect" do
      assert {:ok, redirect} =
               Redirects.create_auto(%{
                 from_path: "/products/old-slug",
                 to_path: "/products/new-slug",
                 source: "auto_slug_change"
               })

      assert redirect.from_path == "/products/old-slug"
      assert redirect.to_path == "/products/new-slug"
      assert redirect.status_code == 301
      assert redirect.source == "auto_slug_change"
      assert redirect.hit_count == 0
    end

    test "is idempotent on conflict" do
      attrs = %{
        from_path: "/products/idempotent",
        to_path: "/products/new",
        source: "auto_slug_change"
      }

      assert {:ok, _} = Redirects.create_auto(attrs)
      assert {:ok, _} = Redirects.create_auto(attrs)

      # Only one redirect exists
      assert [_] = Repo.all(from r in Redirect, where: r.from_path == "/products/idempotent")
    end

    test "flattens redirect chains on creation" do
      # A -> B
      {:ok, _} =
        Redirects.create_auto(%{
          from_path: "/products/a",
          to_path: "/products/b",
          source: "auto_slug_change"
        })

      # B -> C (should flatten A -> C as well)
      {:ok, _} =
        Redirects.create_auto(%{
          from_path: "/products/b",
          to_path: "/products/c",
          source: "auto_slug_change"
        })

      # A should now point directly to C
      assert {:ok, %{to_path: "/products/c"}} = Redirects.lookup("/products/a")
      # B -> C still works
      assert {:ok, %{to_path: "/products/c"}} = Redirects.lookup("/products/b")
    end
  end

  describe "create_manual/1" do
    test "creates a manual redirect" do
      assert {:ok, redirect} =
               Redirects.create_manual(%{
                 from_path: "/old-page",
                 to_path: "/new-page"
               })

      assert redirect.source == "admin"
      assert redirect.status_code == 301
    end
  end

  describe "lookup/1" do
    test "returns redirect when found" do
      {:ok, _} =
        Redirects.create_auto(%{
          from_path: "/products/lookup-test",
          to_path: "/products/new",
          source: "auto_slug_change"
        })

      assert {:ok, %{to_path: "/products/new", status_code: 301}} =
               Redirects.lookup("/products/lookup-test")
    end

    test "returns :not_found when no redirect exists" do
      assert :not_found = Redirects.lookup("/products/nonexistent")
    end
  end

  describe "increment_hit_count/1" do
    test "increments the hit count" do
      {:ok, redirect} =
        Redirects.create_auto(%{
          from_path: "/products/hits-test",
          to_path: "/products/new",
          source: "auto_slug_change"
        })

      assert redirect.hit_count == 0

      Redirects.increment_hit_count(%{id: redirect.id})

      updated = Redirects.get_redirect!(redirect.id)
      assert updated.hit_count == 1
    end
  end

  describe "delete_redirect/1" do
    test "deletes a redirect and clears cache" do
      {:ok, redirect} =
        Redirects.create_auto(%{
          from_path: "/products/delete-test",
          to_path: "/products/new",
          source: "auto_slug_change"
        })

      assert {:ok, %{to_path: "/products/new"}} = Redirects.lookup("/products/delete-test")

      {:ok, _} = Redirects.delete_redirect(redirect)

      assert :not_found = Redirects.lookup("/products/delete-test")
    end
  end

  describe "broken URLs" do
    test "record_broken_url creates a new entry" do
      {:ok, broken_url} = Redirects.record_broken_url("/products/broken", 42)

      assert broken_url.path == "/products/broken"
      assert broken_url.prior_analytics_hits == 42
      assert broken_url.recent_404_count == 1
    end

    test "record_broken_url increments count on existing entry" do
      {:ok, _} = Redirects.record_broken_url("/products/repeat-404", 10)
      {:ok, updated} = Redirects.record_broken_url("/products/repeat-404", 10)

      assert updated.recent_404_count == 2
    end

    test "list_broken_urls returns pending sorted by impact" do
      {:ok, _} = Redirects.record_broken_url("/products/low-traffic", 5)
      {:ok, _} = Redirects.record_broken_url("/products/high-traffic", 100)

      [first, second] = Redirects.list_broken_urls()
      assert first.path == "/products/high-traffic"
      assert second.path == "/products/low-traffic"
    end

    test "ignore_broken_url marks as ignored" do
      {:ok, broken_url} = Redirects.record_broken_url("/products/ignore-me", 1)
      {:ok, ignored} = Redirects.ignore_broken_url(broken_url)

      assert ignored.status == "ignored"
      assert Redirects.list_broken_urls("pending") == []
    end
  end

  describe "prune_stale_redirects/1" do
    test "prunes auto redirects with 0 hits older than threshold" do
      # Insert a stale redirect directly
      {:ok, _} =
        %Redirect{}
        |> Redirect.changeset(%{
          from_path: "/products/stale",
          to_path: "/products/new",
          source: "auto_slug_change"
        })
        |> Repo.insert()

      # Backdate it
      Repo.update_all(
        from(r in Redirect, where: r.from_path == "/products/stale"),
        set: [inserted_at: DateTime.add(DateTime.utc_now(), -100, :day)]
      )

      {:ok, count} = Redirects.prune_stale_redirects(90)
      assert count == 1
      assert :not_found = Redirects.lookup("/products/stale")
    end

    test "preserves redirects with hits" do
      {:ok, redirect} =
        %Redirect{}
        |> Redirect.changeset(%{
          from_path: "/products/has-hits",
          to_path: "/products/new",
          source: "auto_slug_change"
        })
        |> Repo.insert()

      # Add a hit and backdate
      Repo.update_all(
        from(r in Redirect, where: r.id == ^redirect.id),
        set: [hit_count: 5, inserted_at: DateTime.add(DateTime.utc_now(), -100, :day)]
      )

      {:ok, count} = Redirects.prune_stale_redirects(90)
      assert count == 0
    end

    test "preserves manual redirects regardless of age" do
      {:ok, _} =
        Redirects.create_manual(%{
          from_path: "/products/manual-old",
          to_path: "/products/new"
        })

      Repo.update_all(
        from(r in Redirect, where: r.from_path == "/products/manual-old"),
        set: [inserted_at: DateTime.add(DateTime.utc_now(), -200, :day)]
      )

      {:ok, count} = Redirects.prune_stale_redirects(90)
      assert count == 0
    end
  end
end
