"""HTTP tests for GET /api/widget/fragment.

The fragment endpoint is the read-only workhorse — every page view in
overlay/inline mode hits it. These tests pin: cache hit/miss semantics,
404 propagation (vs the old 502-for-everything), key whitelist, and the
`keep` chrome-strip token whitelist.
"""

from odoo.tests import tagged

from odoo.addons.kj_affiliate_widget.controllers import widget_api as _wa
from odoo.addons.kj_affiliate_widget.tests.common import WidgetTestCommon


@tagged("post_install", "-at_install", "kj_affiliate_widget")
class TestWidgetFragment(WidgetTestCommon):

    def _fetch(self, path="/", key=None, keep=None):
        params = {"key": key or self.config.widget_key, "path": path}
        if keep is not None:
            params["keep"] = keep
        url = "/api/widget/fragment?" + "&".join(
            f"{k}={v}" for k, v in params.items()
        )
        return self.url_open(url)

    # ------------------------------------------------------------------
    # Cache semantics
    # ------------------------------------------------------------------

    def test_first_request_records_miss_second_records_hit(self):
        """Two requests with the same (path, keep) → miss then hit."""
        r1 = self._fetch("/de/job-offers")
        self.assertEqual(r1.status_code, 200)
        self.assertEqual(r1.headers.get("X-KJ-Cache"), "miss")
        r2 = self._fetch("/de/job-offers")
        self.assertEqual(r2.status_code, 200)
        self.assertEqual(r2.headers.get("X-KJ-Cache"), "hit")
        with _wa._fragment_cache_lock:
            self.assertEqual(_wa._fragment_cache_stats["hits"], 1)
            self.assertEqual(_wa._fragment_cache_stats["misses"], 1)

    def test_different_keep_param_is_a_different_cache_entry(self):
        """`keep=header` and `keep=` are independent cache keys."""
        self._fetch("/de/job-offers", keep="")
        self._fetch("/de/job-offers", keep="header")
        with _wa._fragment_cache_lock:
            self.assertEqual(_wa._fragment_cache_stats["misses"], 2)
            self.assertEqual(_wa._fragment_cache_stats["hits"], 0)
            self.assertEqual(len(_wa._fragment_cache), 2)

    def test_different_keys_share_same_cache_entry(self):
        """Two affiliates with the same `keep` share one cache row —
        because palette is applied client-side, the rendered HTML is
        byte-identical regardless of `key`."""
        # We can't easily mint a second affiliate without recreating
        # the user-create dance; instead, refetch the same path twice
        # and confirm only one row landed in the cache.
        self._fetch("/de/job-offers")
        self._fetch("/de/job-offers")
        with _wa._fragment_cache_lock:
            self.assertEqual(len(_wa._fragment_cache), 1)

    # ------------------------------------------------------------------
    # Status propagation (G6 — previously masked everything as 502)
    # ------------------------------------------------------------------

    def test_missing_page_returns_404_not_502(self):
        r = self._fetch("/this-path-does-not-exist-xyz-9999")
        self.assertEqual(r.status_code, 404)

    # ------------------------------------------------------------------
    # Key validation
    # ------------------------------------------------------------------

    def test_missing_key_returns_400(self):
        r = self.url_open("/api/widget/fragment?path=/de/job-offers")
        self.assertEqual(r.status_code, 400)

    def test_invalid_key_returns_403(self):
        r = self._fetch("/de/job-offers", key="nope-not-a-real-key")
        self.assertEqual(r.status_code, 403)

    # ------------------------------------------------------------------
    # Path whitelist
    # ------------------------------------------------------------------

    def test_blocks_backend_paths(self):
        for blocked in ("/web/login", "/odoo/action-1", "/admin", "/api/widget/event"):
            r = self._fetch(blocked)
            self.assertEqual(
                r.status_code, 403,
                f"Path {blocked!r} should be blocked (got {r.status_code})",
            )

    # ------------------------------------------------------------------
    # `keep` token whitelist
    # ------------------------------------------------------------------

    def test_keep_token_whitelist(self):
        """Unknown tokens are silently dropped; known ones pass through."""
        # Inject a junk token plus a real one — endpoint should still
        # return 200 (junk is filtered, real one survives).
        r = self._fetch(
            "/de/job-offers",
            keep="header,topbar,injected-junk,<script>,footer",
        )
        self.assertEqual(r.status_code, 200)
