from __future__ import annotations

import json
import re
from html.parser import HTMLParser
from pathlib import Path
from typing import Final, TypedDict, cast, final, override

GUIDE: Final = (
    Path(__file__).resolve().parents[1]
    / "customer-nutrition-onboarding-start-guide.html"
)


class ReadyBoundary(TypedDict):
    customer_enabled: bool
    adaptive_activation: bool
    delivery_enabled: bool


class RecoveryContract(TypedDict):
    latest_card_only: bool
    repeat_taps: bool
    escalate_to: str


class GuideContract(TypedDict):
    version: str
    audience: str
    roles: list[str]
    flow: list[str]
    ready_boundary: ReadyBoundary
    safety_holds: list[str]
    withdrawal_phrase: str
    recovery: RecoveryContract
    pii_policy: str


@final
class GuideParser(HTMLParser):
    def __init__(self) -> None:
        super().__init__()
        self.tags: set[str] = set()
        self.attributes: list[dict[str, str]] = []
        self.contract_chunks: list[str] = []
        self._in_contract: bool = False

    @override
    def handle_starttag(
        self,
        tag: str,
        attrs: list[tuple[str, str | None]],
    ) -> None:
        normalized = {key: value or "" for key, value in attrs}
        self.tags.add(tag)
        self.attributes.append(normalized)
        if tag == "script" and normalized.get("id") == "customer-guide-contract":
            self._in_contract = True

    @override
    def handle_endtag(self, tag: str) -> None:
        if tag == "script" and self._in_contract:
            self._in_contract = False

    @override
    def handle_data(self, data: str) -> None:
        if self._in_contract:
            self.contract_chunks.append(data)


def load_guide() -> tuple[str, GuideParser, GuideContract]:
    assert GUIDE.is_file(), "customer-only guide does not exist"
    source = GUIDE.read_text(encoding="utf-8")
    parser = GuideParser()
    parser.feed(source)
    contract = cast(
        GuideContract,
        json.loads("".join(parser.contract_chunks)),
    )
    return source, parser, contract


def test_customer_guide_contains_complete_current_flow() -> None:
    source, parser, contract = load_guide()

    assert contract["version"] == "customer-nutrition-start-v1"
    assert contract["audience"] == "customer"
    assert contract["roles"] == ["customer"]
    assert contract["flow"] == [
        "privacy-v1-consent",
        "baseline-and-restriction-questionnaire",
        "customer-attestation",
        "human-safety-review-wait",
        "ready",
        "explicit-operator-activation",
    ]
    assert contract["ready_boundary"] == {
        "customer_enabled": False,
        "adaptive_activation": False,
        "delivery_enabled": False,
    }
    role_tabs = {
        attributes.get("data-role")
        for attributes in parser.attributes
        if "data-role" in attributes
    }
    assert role_tabs <= {"customer"}
    assert "trainer-workflow" not in source
    assert "owner-workflow" not in source


def test_customer_guide_explains_safety_and_recovery() -> None:
    _, _, contract = load_guide()

    assert contract["safety_holds"] == [
        "underage",
        "implausible-measurement",
        "unsafe-goal",
        "unknown-restriction",
        "medical-condition",
        "medication",
    ]
    assert contract["withdrawal_phrase"] == "서비스 중단 및 동의 철회 요청"
    assert contract["recovery"] == {
        "latest_card_only": True,
        "repeat_taps": False,
        "escalate_to": "room-owner",
    }


def test_customer_guide_is_mobile_accessible() -> None:
    source, parser, contract = load_guide()

    assert re.search(r"<html[^>]+lang=[\"']ko[\"']", source)
    assert re.search(
        r"<meta[^>]+name=[\"']viewport[\"'][^>]+width=device-width",
        source,
    )
    assert {"header", "nav", "main", "footer"} <= parser.tags
    assert "@media (max-width:" in source
    assert "min-height: 44px" in source
    assert "prefers-reduced-motion: reduce" in source
    assert "overflow-x: clip" in source
    assert contract["pii_policy"] == "no-example-personal-data"
    assert not re.search(r"\b01[016789]-\d{3,4}-\d{4}\b", source)
    assert not re.search(r"[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}", source)
    assert not re.search(r"<(?:script|img)[^>]+(?:src)=[\"']https?://", source)
    assert not re.search(r"<link[^>]+href=[\"']https?://", source)
