"""Track C — Theme tab fields (24+).

The theme tab is where an affiliate paints the embedded KJ site with
their brand. Each field either:
  - explicit override: a hex/text/number written directly into the
    generated theme.css (the high-confidence wiring), or
  - "auto" mode: the field is empty and the value is derived from
    `theme_color` via `generate_palette()`.

Each test below sets ONE explicit value, fetches
`/api/widget/css/{key}/theme.css`, and asserts the value appears in
the rendered CSS. If a field is supposed to drive UI but its set
value never appears, the field is effectively dead and the test
fails — exactly the regression shape we're guarding against.

Two regression-anchor tests pin the structural wins from the prior
investigation:

  - `test_c_no_body_selectors`: theme.css must NOT use a bare ``body``
    selector prefix. The fragment is injected into a shadow root that
    drops ``<body>`` (innerHTML semantics), so ``body .kj-*`` silently
    no-ops — that's what caused the navbar action buttons to render
    white-on-white. Use ``#wrapwrap`` instead.

  - `test_c_wrapwrap_overrides_present`: the CSS must include the
    ``#wrapwrap .kj-action-btn`` and ``#wrapwrap .kj-stelle-finden-btn``
    correctives that beat KJ's own !important navbar rules.
"""

import re

from odoo.tests import tagged

from odoo.addons.kj_affiliate_widget.tests.common import WidgetTestCommon


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

    # ------------------------------------------------------------------
    # Helper — set the field, fetch CSS, return text
    # ------------------------------------------------------------------

    def _css(self, **overrides):
        """Write theme fields, fetch theme.css for this config's key."""
        if overrides:
            self.config.write(overrides)
        r = self.url_open(
            f"/api/widget/css/{self.config.widget_key}/theme.css"
            f"?bust={hash(tuple(sorted(overrides.items())))}"
        )
        self.assertEqual(
            r.status_code, 200,
            f"theme.css endpoint failed: {r.status_code} {r.text[:200]}",
        )
        return r.text

    def _assert_color_in_css(self, css, hex_value, field_name):
        """Assert the hex (or its rgb form) shows up somewhere in the CSS."""
        hex_value = hex_value.lower()
        present = hex_value in css.lower()
        if not present:
            # Some emitters convert hex → rgb(); check that form too.
            r, g, b = int(hex_value[1:3], 16), int(hex_value[3:5], 16), int(hex_value[5:7], 16)
            rgb_form = f"rgb({r}, {g}, {b})"
            present = rgb_form in css
        self.assertTrue(
            present,
            f"Field {field_name}={hex_value!r} was set but its value "
            f"never appears in theme.css. The field is wired in the "
            f"backend form but doesn't actually drive any CSS rule.",
        )

    # ==================================================================
    # Regression anchors — the gap that motivated this track
    # ==================================================================

    def test_c_no_body_selectors(self):
        """theme.css must not use a bare `body` selector prefix.

        The embed fragment is injected into a shadow root, which drops
        `<body>` during innerHTML parsing. Rules like `body .kj-action-btn`
        silently no-op in shadow scope — exactly how the navbar action
        buttons rendered white-on-white in the screenshot that prompted
        this investigation.
        """
        css = self._css()
        # Match `body ` followed by class/id/element selector at start of line
        # (multiline). Combinator anchors `body .X` / `body #X` / `body X`.
        bad = re.findall(r"(?:^|\n)body[ ]+[.#a-zA-Z]", css)
        self.assertFalse(
            bad,
            f"theme.css contains {len(bad)} bare `body ` selector(s): "
            f"{bad[:5]}. Use `#wrapwrap ` so the rule fires inside the "
            "widget's shadow DOM as well as on origin.",
        )

    def test_c_wrapwrap_overrides_present(self):
        """Both navbar-button correctives must be in the rendered CSS."""
        css = self._css()
        self.assertIn(
            "#wrapwrap .kj-action-btn",
            css,
            ".kj-action-btn override missing — Merkliste / Stelle "
            "veröffentlichen buttons will render white-on-white in the "
            "embed because KJ's navbar SCSS sets color:#fff !important.",
        )
        self.assertIn(
            "#wrapwrap .kj-stelle-finden-btn",
            css,
            ".kj-stelle-finden-btn override missing — main CTA loses its "
            "affiliate-branded fill in the embed.",
        )

    # ==================================================================
    # Base theme (3)
    # ==================================================================

    def test_c01_theme_color_drives_palette(self):
        """Set theme_color → the generated palette button colour
        (= theme_color verbatim) appears in the CSS."""
        css = self._css(theme_color="#ff8800")
        self._assert_color_in_css(css, "#ff8800", "theme_color (palette.button)")

    def test_c02_theme_font_size_appears(self):
        css = self._css(theme_font_size=21)
        self.assertIn(
            "21px", css,
            "theme_font_size=21 should land in the body font-size rule",
        )

    def test_c03_theme_font_family_open_sans(self):
        css = self._css(theme_font_family="open-sans")
        # The CSS emits a font-family stack including the chosen family
        # somewhere — let the test match either "Open Sans" or "open-sans".
        self.assertRegex(
            css, r"(open[-]sans|Open Sans)",
            "theme_font_family=open-sans not applied in theme.css",
        )

    # ==================================================================
    # Explicit color overrides (6) — each must override its palette slot
    # ==================================================================

    def test_c04_theme_bg_color_override(self):
        self._assert_color_in_css(self._css(theme_bg_color="#ffe4e1"), "#ffe4e1", "theme_bg_color")

    def test_c05_theme_heading_color_override(self):
        self._assert_color_in_css(self._css(theme_heading_color="#102030"), "#102030", "theme_heading_color")

    def test_c06_theme_text_color_override(self):
        self._assert_color_in_css(self._css(theme_text_color="#2c3e50"), "#2c3e50", "theme_text_color")

    def test_c07_theme_link_color_override(self):
        self._assert_color_in_css(self._css(theme_link_color="#8e44ad"), "#8e44ad", "theme_link_color")

    def test_c08_theme_btn_color_override(self):
        self._assert_color_in_css(self._css(theme_btn_color="#16a085"), "#16a085", "theme_btn_color")

    def test_c09_theme_border_color_override(self):
        self._assert_color_in_css(self._css(theme_border_color="#bdc3c7"), "#bdc3c7", "theme_border_color")

    # ==================================================================
    # Navbar (3)
    # ==================================================================

    def test_c10_theme_navbar_scheme_dark_emits_dark_rule(self):
        css = self._css(theme_navbar_scheme="dark")
        # Dark scheme paints navbar dark; verify a navbar rule mentions
        # a dark colour and the scheme is wired (not silently ignored).
        self.assertRegex(
            css, r"\.kj-(header|navbar)|kj-main-menu",
            "navbar scheme=dark should produce navbar-targeting rules",
        )

    def test_c11_theme_navbar_bg_custom(self):
        css = self._css(theme_navbar_scheme="custom", theme_navbar_bg="#0b1d3a")
        self._assert_color_in_css(css, "#0b1d3a", "theme_navbar_bg (custom)")

    def test_c12_theme_navbar_text_custom(self):
        css = self._css(theme_navbar_scheme="custom", theme_navbar_text="#f5d000")
        self._assert_color_in_css(css, "#f5d000", "theme_navbar_text (custom)")

    # ==================================================================
    # Footer (5)
    # ==================================================================

    def test_c13_theme_footer_mode_hidden_hides_footer(self):
        css = self._css(theme_footer_mode="hidden")
        self.assertRegex(
            css, r"footer\s*\{[^}]*display:\s*none",
            "theme_footer_mode=hidden should emit `footer { display: none }`",
        )

    def test_c14_theme_footer_mode_full_shows_footer(self):
        css = self._css(theme_footer_mode="full")
        self.assertRegex(
            css, r"footer\s*\{[^}]*display:\s*block",
            "theme_footer_mode=full should emit `footer { display: block }`",
        )

    def test_c15_theme_site_footer_scheme_dark(self):
        css = self._css(theme_footer_mode="full", theme_site_footer_scheme="dark")
        # Dark scheme paints footer dark — implementation uses #212529
        self.assertIn(
            "#212529", css,
            "site_footer_scheme=dark should emit a dark footer rule",
        )

    def test_c16_theme_site_footer_bg_custom(self):
        css = self._css(
            theme_footer_mode="full",
            theme_site_footer_scheme="custom",
            theme_site_footer_bg="#3c1361",
        )
        self._assert_color_in_css(css, "#3c1361", "theme_site_footer_bg")

    def test_c17_theme_site_footer_text_custom(self):
        css = self._css(
            theme_footer_mode="full",
            theme_site_footer_scheme="custom",
            theme_site_footer_text="#eeeeee",
        )
        self._assert_color_in_css(css, "#eeeeee", "theme_site_footer_text")

    # ==================================================================
    # Cards & forms (4)
    # ==================================================================

    def test_c18_theme_card_bg_override(self):
        self._assert_color_in_css(self._css(theme_card_bg="#fafafa"), "#fafafa", "theme_card_bg")

    def test_c19_theme_card_header_bg_override(self):
        self._assert_color_in_css(self._css(theme_card_header_bg="#dde2eb"), "#dde2eb", "theme_card_header_bg")

    def test_c20_theme_input_bg_override(self):
        self._assert_color_in_css(self._css(theme_input_bg="#f0f8ff"), "#f0f8ff", "theme_input_bg")

    def test_c21_theme_form_bg_override(self):
        self._assert_color_in_css(self._css(theme_form_bg="#fff8dc"), "#fff8dc", "theme_form_bg")

    # ==================================================================
    # Content visibility (3) — booleans flipping show/hide CSS
    # ==================================================================

    def test_c22_theme_show_search_false_hides_search(self):
        css = self._css(theme_show_search=False)
        # Implementation hides the search via display:none on a search class.
        self.assertRegex(
            css, r"(kj-search|search-bar|kj-jobs-search)[^{]*\{[^}]*display:\s*none",
            "theme_show_search=False should hide the search bar",
        )

    def test_c23_theme_show_logo_false_hides_logo(self):
        css = self._css(theme_show_logo=False)
        self.assertRegex(
            css, r"(kj-logo|kj-header-logo)[^{]*\{[^}]*display:\s*none",
            "theme_show_logo=False should hide the KJ logo",
        )

    def test_c24_theme_show_newsletter_popup_default_false_hides(self):
        """Default is False — newsletter popup must be hidden when off."""
        css = self._css(theme_show_newsletter_popup=False)
        self.assertRegex(
            css, r"(newsletter|kj-newsletter-popup)[^{]*\{[^}]*display:\s*none",
            "theme_show_newsletter_popup=False should hide the popup",
        )

    # ==================================================================
    # Dead-field removal guard
    # ==================================================================

    def test_c25_dead_theme_fields_removed(self):
        """theme_custom_title and theme_no_results_text were stored but
        never read by any endpoint (confirmed by an earlier dead-field
        audit). They've been removed from the model.

        This guard fails if someone re-adds either field without wiring
        it — re-introducing the stored-but-dead shape. If a field is
        genuinely needed, add it AND a positive test that proves it
        drives the rendered CSS.
        """
        model_fields = self.env["affiliate.widget.config"]._fields
        for dead in ("theme_custom_title", "theme_no_results_text"):
            self.assertNotIn(
                dead, model_fields,
                f"{dead} was re-added to the model. It was removed as a "
                "stored-but-dead field — if it's needed now, wire it to "
                "theme.css and add a positive Track C test.",
            )
