"""Rendering a design system as its master CSS sheet.

The generator is pure, so a whole sheet is one literal of tokens in and a string
out. Two things here are load-bearing rather than cosmetic: what the sheet
deliberately does NOT contain, and the fact that a value cannot escape its
declaration.
"""
from types import SimpleNamespace

from scribe.services.design_stylesheet import (
    check_code_against_tokens,
    derivation_report,
    duplicate_values,
    is_valid_token_name,
    render_stylesheet,
    safe_comment,
    safe_value,
    selector_for_mode,
)
from tests.helpers import design_token_stub


# --- safety -----------------------------------------------------------------
#
# Design systems are shareable records. A value that can close its declaration
# can inject arbitrary CSS into the page of anyone the system was shared with,
# which makes this a real boundary rather than tidiness.

def test_a_value_that_would_escape_its_declaration_is_refused():
    """`red; } body { display: none` is the whole attack: end the declaration,
    close the block, open your own."""
    assert safe_value("red; } body { display: none") is None
    assert safe_value("#fff}") is None
    assert safe_value("#fff;") is None


def test_at_rules_and_tags_are_refused():
    assert safe_value("@import url(evil.css)") is None
    assert safe_value("</style><script>") is None


def test_comment_delimiters_in_a_value_are_refused():
    """A value is not rendered inside a comment, but `/*` would comment out
    every declaration after it — silently blanking the rest of the sheet."""
    assert safe_value("red /* ") is None
    assert safe_value("*/ red") is None


def test_a_newline_in_a_value_is_refused():
    assert safe_value("red\n  color: blue") is None


def test_ordinary_values_survive_untouched():
    for value in ("#14171a", "8px", "cubic-bezier(0.2, 0.6, 0.2, 1)",
                  "0 4px 12px rgba(0,0,0,0.35)", "color-mix(in srgb, red 15%, transparent)"):
        assert safe_value(value) == value


def test_a_rejected_value_is_dropped_not_cleaned_up():
    """Rejecting beats stripping. A partially-sanitised value is one the operator
    never wrote, and the sheet's entire claim is that it IS the record — quietly
    rendering a different colour would break that claim invisibly."""
    css = render_stylesheet([design_token_stub(name="--fs-x", value_by_mode={"base": "red; } body { color: blue"})])
    assert "body" not in css
    assert "value rejected" in css


def test_comment_text_cannot_close_its_comment():
    """`purpose` is operator prose rendered into a comment — `*/` in it would
    end the comment and spill the rest into the stylesheet as code."""
    assert "*/" not in safe_comment("ends the comment */ then color: red")


def test_a_malformed_token_name_is_dropped():
    """A name is an identifier. A 'cleaned up' identifier is a different token
    than the one recorded, so it is dropped rather than repaired."""
    assert is_valid_token_name("--fs-obsidian")
    assert not is_valid_token_name("--fs obsidian")
    assert not is_valid_token_name("color: red")
    assert not is_valid_token_name("fs-obsidian")     # no leading --

    css = render_stylesheet([design_token_stub(name="--bad name", value_by_mode={"base": "red"})])
    assert "bad name" not in css


def test_a_mode_name_cannot_break_out_of_its_selector():
    assert selector_for_mode('dark"] body {') == '[data-theme="darkbody"]'


# --- what the sheet is ------------------------------------------------------

def test_the_sheet_declares_properties_and_styles_no_elements():
    """THE shape decision. A sheet that styled elements would restate the same
    handful of values once per element and grow with the UI. Purpose tokens are
    stated once and reused; components are snippets that reference them."""
    css = render_stylesheet([
        design_token_stub(name="--fs-obsidian", value_by_mode={"base": "#14171a"}, group_name="surface"),
        design_token_stub(name="--fs-moss", value_by_mode={"base": "#4a5d3f"}, group_name="action"),
    ])
    assert "--fs-obsidian: #14171a;" in css
    # No element or class rules — the sheet has exactly one block here, and
    # every declaration in it is a custom property.
    assert css.count("{") == 1
    declarations = [
        line.strip() for line in css.splitlines()
        if ":" in line and line.strip().endswith(";")
    ]
    assert declarations and all(d.startswith("--") for d in declarations)


def test_the_header_says_what_the_sheet_is_for():
    """A generated file with no explanation gets hand-edited, and then it has
    diverged from the record it claims to be."""
    css = render_stylesheet([design_token_stub(name="--fs-x", value_by_mode={"base": "1px"})], title="FabledSword")
    assert "FabledSword" in css
    assert "Generated" in css
    assert "snippets" in css


# --- modes ------------------------------------------------------------------

def test_base_goes_on_the_root_selector_and_other_modes_layer_over_it():
    """Matches the convention already in the codebase, and the one-way scoping
    #251 recorded: light on `:root`, dark layered on an attribute selector."""
    css = render_stylesheet([
        design_token_stub(name="--fs-bg", value_by_mode={"base": "#f5f1e8", "dark": "#14171a"}),
    ])
    assert ":root {" in css
    assert '[data-theme="dark"] {' in css
    assert css.index(":root {") < css.index('[data-theme="dark"] {')


def test_a_mode_block_contains_only_what_that_mode_declares():
    """A mode block is an OVERRIDE layer, exactly as the storage model has it.
    Repeating every token in every block would make the sheet claim each mode
    redefines the whole system."""
    css = render_stylesheet([
        design_token_stub(name="--fs-bg", value_by_mode={"base": "#f5f1e8", "dark": "#14171a"}),
        design_token_stub(name="--fs-radius-md", value_by_mode={"base": "8px"}),
    ])
    dark_block = css.split('[data-theme="dark"] {')[1]
    assert "--fs-bg" in dark_block
    assert "--fs-radius-md" not in dark_block


def test_the_root_selector_is_caller_chosen():
    """A container-scoped preview cannot use `:root`. A generator that hardcoded
    it could not serve the preview surface at all."""
    css = render_stylesheet(
        [design_token_stub(name="--fs-x", value_by_mode={"base": "1px"})], root_selector="[data-preview]"
    )
    assert "[data-preview] {" in css
    assert ":root {" not in css


# --- grouping and honesty ---------------------------------------------------

def test_tokens_are_grouped_by_purpose_with_the_group_named():
    css = render_stylesheet([
        design_token_stub(name="--fs-obsidian", value_by_mode={"base": "#14171a"}, group_name="surface"),
        design_token_stub(name="--fs-radius-md", value_by_mode={"base": "8px"}, group_name="radius"),
    ])
    assert "/* surface */" in css
    assert "/* radius */" in css


def test_a_purpose_becomes_an_inline_comment_on_the_base_layer_only():
    """Repeating the same prose in every mode block is noise: the token means
    the same thing in dark mode."""
    css = render_stylesheet([
        design_token_stub(name="--fs-obsidian", value_by_mode={"base": "#14171a", "dark": "#000000"}, purpose="page bg, deepest surface"),
    ])
    assert css.count("page bg, deepest surface") == 1


def test_a_declared_token_with_no_value_appears_as_a_comment_not_a_silence():
    """The rulebook named it, so its absence is a FINDING. A commented line puts
    that finding where the reader is already looking; dropping it would make the
    sheet look complete."""
    css = render_stylesheet([
        design_token_stub(name="--fs-obsidian", value_by_mode={"base": "#14171a"}),
        design_token_stub(name="--fs-radius-sm", value_by_mode={}),
    ])
    assert "--fs-radius-sm" in css
    assert "no value set yet" in css
    # Commented, so it cannot be mistaken for a live declaration.
    assert "  --fs-radius-sm:" not in css


def test_an_empty_system_renders_a_sheet_with_no_blocks_rather_than_failing():
    """A system with no tokens is an ordinary state (rule #115), including one
    that was just created."""
    css = render_stylesheet([])
    assert "{" not in css
    assert "Generated" in css


# --- the reuse report -------------------------------------------------------

def test_two_tokens_sharing_a_value_are_reported_not_refused():
    """The operator's constraint, made checkable: reuse consistent values rather
    than restating them. But a design system legitimately aligns colours on
    purpose — "Success = Moss, by design" — so this reports and lets a human
    decide which it is."""
    dupes = duplicate_values([
        design_token_stub(name="--fs-moss", value_by_mode={"base": "#4A5D3F"}),
        design_token_stub(name="--fs-success", value_by_mode={"base": "#4a5d3f"}),
        design_token_stub(name="--fs-obsidian", value_by_mode={"base": "#14171a"}),
    ])
    assert dupes == {"#4a5d3f": ["--fs-moss", "--fs-success"]}


def test_tokens_that_agree_in_one_mode_but_differ_in_another_are_not_duplicates():
    """A near-miss is a different, weaker finding, and calling it a duplicate
    would send someone to merge two tokens that genuinely diverge."""
    assert duplicate_values([
        design_token_stub(name="--fs-a", value_by_mode={"base": "#fff", "dark": "#000"}),
        design_token_stub(name="--fs-b", value_by_mode={"base": "#fff", "dark": "#111"}),
    ]) == {"#fff": ["--fs-a", "--fs-b"]}


def test_valueless_tokens_never_count_as_duplicates_of_each_other():
    """Otherwise every unfilled token would collide with every other one and the
    report would be nothing but noise on a fresh import."""
    assert duplicate_values([design_token_stub(name="--fs-a", value_by_mode={}), design_token_stub(name="--fs-b", value_by_mode={})]) == {}


# --- reading the sheet from the other side ----------------------------------
#
# "The snippets use the tags from the sheet" made checkable. All three findings
# below are currently SILENT in this codebase — no error, no failing test.

def _tok(name, base=None, supersedes=()):
    return SimpleNamespace(
        name=name,
        value_by_mode={"base": base} if base else {},
        supersedes=list(supersedes),
        group_name=None, purpose=None,
    )


SHEET = [
    _tok("--fs-obsidian", "#14171a"),
    _tok("--fs-parchment", "#e8e4d8", supersedes=["#fff", "#ffffff"]),
]


def test_a_reference_to_a_token_that_does_not_exist_is_reported():
    """THE bug this exists for. `var(--color-accent)` where no such token exists
    renders as nothing at all — invisible focus rings, unstyled elements, no
    error and no failing test. It happened in this codebase this session."""
    report = check_code_against_tokens("outline: 2px solid var(--color-accent);", SHEET)
    assert report["unknown"] == ["--color-accent"]
    assert report["used"] == []


def test_a_reference_that_resolves_is_reported_as_used_not_as_a_finding():
    report = check_code_against_tokens("background: var(--fs-obsidian);", SHEET)
    assert report["used"] == ["--fs-obsidian"]
    assert report["unknown"] == []


def test_a_superseded_literal_is_found_and_names_its_replacement():
    """The reframe paying off end to end: the finding says what to WRITE, not
    merely what is wrong. That is only possible because the token declared it."""
    report = check_code_against_tokens("color: #fff;", SHEET)
    assert report["superseded_literals"] == [
        {"literal": "#fff", "use_instead": "--fs-parchment"}
    ]


def test_a_shorter_hex_does_not_match_inside_a_longer_one():
    """`#fff` must not fire on `#ffffff` — different colours, and a finding on
    the wrong one sends someone to change code that was already correct."""
    report = check_code_against_tokens("color: #ffffffee;", SHEET)
    assert [f["literal"] for f in report["superseded_literals"]] == []


def test_the_literal_match_is_case_insensitive():
    """A record writes `#FFFFFF` and code writes `#ffffff`. A case-sensitive
    check would silently find nothing — the same trap `normalizeColour` in
    utils/designDrift.ts exists for on the client side."""
    report = check_code_against_tokens("color: #FFFFFF;", SHEET)
    assert report["superseded_literals"] == [
        {"literal": "#ffffff", "use_instead": "--fs-parchment"}
    ]


def test_a_token_defined_and_read_locally_is_reported_once_not_twice():
    """A snippet minting its own custom property is the bloat a shared sheet
    exists to prevent — but when it also reads it back, that is ONE fact. The
    unknown-reference check owns it, because "--btn-bg does not exist in the
    sheet" is the more precise statement of the same problem."""
    report = check_code_against_tokens(".btn { --btn-bg: #333; background: var(--btn-bg); }", SHEET)
    assert report["unknown"] == ["--btn-bg"]
    assert report["local_definitions"] == []


def test_a_local_definition_never_read_back_is_still_reported():
    report = check_code_against_tokens(".btn { --btn-bg: #333; }", SHEET)
    assert report["local_definitions"] == ["--btn-bg"]


def test_a_declaration_that_is_not_a_custom_property_is_not_mistaken_for_one():
    report = check_code_against_tokens(".btn { color: red; background: blue; }", SHEET)
    assert report["local_definitions"] == []


def test_clean_code_reports_nothing_to_act_on():
    report = check_code_against_tokens(
        ".btn { background: var(--fs-obsidian); color: var(--fs-parchment); }", SHEET
    )
    assert report["unknown"] == []
    assert report["superseded_literals"] == []
    assert report["local_definitions"] == []
    assert report["used"] == ["--fs-obsidian", "--fs-parchment"]


def test_the_inline_comment_falls_back_to_rationale_when_there_is_no_purpose():
    """A token carrying only the why still says something in the sheet, rather
    than rendering bare because the other field happened to be empty."""
    token = SimpleNamespace(
        name="--fs-success", value_by_mode={"base": "#4a5d3f"},
        group_name=None, purpose=None, rationale="equals Moss, by design",
    )
    assert "equals Moss, by design" in render_stylesheet([token])


def test_purpose_wins_over_rationale_in_the_comment():
    """What a token is FOR is what a reader of the stylesheet needs first."""
    token = SimpleNamespace(
        name="--fs-x", value_by_mode={"base": "1px"},
        group_name=None, purpose="hairline borders", rationale="because thin",
    )
    css = render_stylesheet([token])
    assert "hairline borders" in css
    assert "because thin" not in css


# --- derivation -------------------------------------------------------------
#
# A formula needs no special storage: `color-mix(..., var(--fs-accent) 15%, ...)`
# is a value, and the browser resolves it live. What it needs is a check, because
# a formula pointing at a missing token is dropped silently.

def _dtok(name, base):
    return SimpleNamespace(
        name=name, value_by_mode={"base": base},
        group_name=None, purpose=None, supersedes=[],
    )


ACCENT = "color-mix(in srgb, var(--fs-accent) 15%, transparent)"


def test_a_formula_survives_the_value_sanitiser_untouched():
    """LOAD-BEARING for the whole approach. If `color-mix(... var(...) ...)` were
    rejected as unsafe, derivation would need a storage shape of its own."""
    assert safe_value(ACCENT) == ACCENT
    assert ACCENT in render_stylesheet([_dtok("--fs-accent-soft", ACCENT)])


def test_a_derived_token_reports_what_it_is_computed_from():
    report = derivation_report([
        _dtok("--fs-accent", "#5b4a8a"),
        _dtok("--fs-accent-soft", ACCENT),
    ])
    assert report["derived"] == {"--fs-accent-soft": ["--fs-accent"]}
    assert report["unknown_refs"] == {}


def test_a_formula_pointing_at_a_token_that_does_not_exist_is_reported():
    """The browser drops the whole declaration — invalid at computed-value time —
    and nothing errors. Exactly the failure mode this system exists to end."""
    report = derivation_report([_dtok("--fs-accent-soft", ACCENT)])
    assert report["unknown_refs"] == {"--fs-accent-soft": ["--fs-accent"]}


def test_a_plain_value_is_not_reported_as_derived():
    report = derivation_report([_dtok("--fs-obsidian", "#14171a")])
    assert report["derived"] == {}


def test_a_derivation_loop_is_reported_once():
    """CSS resolves a loop to nothing rather than hanging, so this is about
    telling the operator — but a token that quietly resolves to nothing is
    precisely the thing worth being told."""
    report = derivation_report([
        _dtok("--fs-a", "var(--fs-b)"),
        _dtok("--fs-b", "var(--fs-a)"),
    ])
    assert len(report["cycles"]) == 1
    assert set(report["cycles"][0]) == {"--fs-a", "--fs-b"}


def test_a_chain_of_derivations_is_not_a_cycle():
    """a <- b <- c is ordinary and must not trip the loop check."""
    report = derivation_report([
        _dtok("--fs-a", "#000"),
        _dtok("--fs-b", "var(--fs-a)"),
        _dtok("--fs-c", "var(--fs-b)"),
    ])
    assert report["cycles"] == []
    assert report["derived"] == {"--fs-b": ["--fs-a"], "--fs-c": ["--fs-b"]}


def test_a_token_referencing_itself_is_not_treated_as_a_dependency():
    """`--fs-x: var(--fs-x, fallback)` is a self-reference with a fallback, not a
    derivation — counting it would report every such token as a one-node loop."""
    report = derivation_report([_dtok("--fs-x", "var(--fs-x, 8px)")])
    assert report["cycles"] == []
    assert report["derived"] == {}


def test_a_derived_token_needs_only_a_base_value_to_follow_every_mode():
    """The reason formulas beat computed literals. One declaration in the base
    layer tracks its source through dark mode too, because `var()` resolves where
    it is USED, not where it is written — so there is nothing to re-derive when a
    colour changes."""
    css = render_stylesheet([
        SimpleNamespace(
            name="--fs-accent", value_by_mode={"base": "#5b4a8a", "dark": "#7a68b0"},
            group_name=None, purpose=None,
        ),
        _dtok("--fs-accent-soft", ACCENT),
    ])
    dark_block = css.split('[data-theme="dark"] {')[1]
    assert "--fs-accent:" in dark_block
    assert "--fs-accent-soft" not in dark_block   # stated once, follows anyway
