"""Проверки аудиоупаковки и структуры учебника. Реальной сети/браузера нет."""
import base64
import contextlib
import email.policy
from email.parser import BytesParser
from html.parser import HTMLParser
import html
import importlib.util
import io
import json
import hashlib
from pathlib import Path
import re
import subprocess
import tempfile
import unittest
from unittest.mock import patch
from urllib.parse import unquote, urlparse
import urllib.request
import xml.etree.ElementTree as ET

REPO = Path(__file__).resolve().parents[2]
BOOK = REPO / "docs/codex-cli-voice-textbook.html"


class Structure(HTMLParser):
    def __init__(self):
        super().__init__(convert_charrefs=True)
        self.ids, self.links, self.stack, self.rects, self.chapters = {}, [], [], [], []
        self.h1 = 0
    def handle_starttag(self, tag, attrs):
        attrs = dict(attrs)
        if "id" in attrs:
            if attrs["id"] in self.ids:
                raise AssertionError("duplicate ID " + attrs["id"])
            self.ids[attrs["id"]] = attrs
        if tag == "a":
            self.links.append(attrs.get("href", ""))
        if tag == "h1": self.h1 += 1
        if "chapter" in attrs.get("class", "").split(): self.chapters.append(attrs["id"])
        if tag == "rect" and any(t == "figure" for t, _ in self.stack):
            self.rects.append(any(t == "a" and a.get("href", "").startswith("#") for t, a in self.stack))
        if tag == "img":
            src = attrs.get("src", "")
            if not src.startswith("data:image/png;base64,") or not attrs.get("alt"):
                raise AssertionError("image must be embedded PNG with alt")
            data = base64.b64decode(src.split(",", 1)[1], validate=True)
            if not data.startswith(b"\x89PNG\r\n\x1a\n") or len(data) > 2_000_000:
                raise AssertionError("invalid or oversized screenshot")
        if tag in ("script", "iframe", "link") and ("src" in attrs or "href" in attrs):
            raise AssertionError("external executable/asset")
        if tag not in ("meta", "br", "input", "hr", "link", "img"):
            self.stack.append((tag, attrs))
    def handle_startendtag(self, tag, attrs):
        self.handle_starttag(tag, attrs)
        if tag not in ("meta", "br", "input", "hr", "link", "img"):
            self.handle_endtag(tag)
    def handle_endtag(self, tag):
        if not self.stack or self.stack.pop()[0] != tag:
            raise AssertionError("unbalanced tag " + tag)


class TextbookTests(unittest.TestCase):
    def setUp(self):
        self.source = BOOK.read_text()
        self.parsed = Structure()
        self.parsed.feed(self.source)

    def test_unique_ids_balanced_autonomous_and_original_anchors(self):
        self.assertFalse(self.parsed.stack)
        self.assertEqual(self.parsed.h1, 1)
        self.assertTrue({"overview", "cli", "protocols", "demo", "launch", "evidence"}.issubset(self.parsed.chapters))
        self.assertEqual(len(self.parsed.chapters), 29)
        self.assertIn("ui-entrypoints", self.parsed.chapters)

    def test_every_svg_block_is_real_anchor_and_paths_exist(self):
        self.assertTrue(self.parsed.rects)
        self.assertTrue(all(self.parsed.rects))
        for url in self.parsed.links:
            with self.subTest(url=url):
                if url.startswith("#"):
                    self.assertIn(unquote(url[1:]), self.parsed.ids)
                elif url.startswith("file:"):
                    self.assertTrue(Path(unquote(urlparse(url).path)).exists(), url)
                else:
                    if url.startswith("https://"):
                        continue
                    self.assertFalse(urlparse(url).scheme, url)
                    target = (BOOK.parent / unquote(urlparse(url).path)).resolve()
                    self.assertTrue(target.is_file(), url)
                    if target.suffix == ".html" and urlparse(url).fragment:
                        parsed = Structure()
                        parsed.feed(target.read_text())
                        self.assertIn(unquote(urlparse(url).fragment), parsed.ids)

    def test_js_syntax_and_shell_command_blocks(self):
        scripts = re.findall(r"<script>(.*?)</script>", self.source, re.S)
        self.assertEqual(len(scripts), 1)
        result = subprocess.run(["env", "-u", "NODE_OPTIONS", "node", "--check"], input=scripts[0], text=True, capture_output=True)
        self.assertEqual(result.returncode, 0, result.stderr)
        commands = re.findall(r'<pre><code class="language-bash"[^>]*>(.*?)</code></pre>', self.source, re.S)
        self.assertGreaterEqual(len(commands), 14)
        for command in commands:
            result = subprocess.run(["bash", "-n"], input=html.unescape(re.sub('<[^>]+>', '', command)), text=True, capture_output=True)
            self.assertEqual(result.returncode, 0, result.stderr)

    def test_svg_xml_and_two_schemes_per_audio_method(self):
        diagrams = [svg for svg in re.findall(r"<svg\b.*?</svg>", self.source, re.S) if 'role="img"' in svg]
        self.assertGreaterEqual(len(diagrams), 20)
        for svg in diagrams:
            ET.fromstring(svg)
        for chapter in ["demo", "stt-http", "tts-http", "tts-read-aloud", "tts-realtime", "tts-appserver", "tts-local", "voice-console"]:
            match = re.search(r'<section id="' + chapter + r'".*?</section>', self.source, re.S)
            self.assertGreaterEqual(match[0].count('<figure'), 2, chapter)

    def test_entrypoint_page_navigation_screenshots_and_code_map(self):
        page = BOOK
        source = self.source
        parsed = Structure()
        parsed.feed(source)
        self.assertFalse(parsed.stack)
        self.assertEqual(parsed.h1, 1)
        self.assertTrue(all(parsed.rects))
        parsed_files={page.resolve():parsed}
        for url in parsed.links:
            parts = urlparse(url)
            if parts.scheme == "https":
                continue
            self.assertFalse(parts.scheme, url)
            target = page if not parts.path else (page.parent / unquote(parts.path)).resolve()
            self.assertTrue(target.is_file(), url)
            if target.suffix == ".html" and parts.fragment:
                if target not in parsed_files:
                    target_parsed = Structure()
                    target_parsed.feed(target.read_text())
                    parsed_files[target]=target_parsed
                target_parsed=parsed_files[target]
                self.assertIn(unquote(parts.fragment), target_parsed.ids, url)
        self.assertIn('href="audio-ui-entrypoints.html#mic"', self.source)
        self.assertIn('href="#ui-entrypoints"', source)
        for svg in re.findall(r"<svg\b.*?</svg>", source, re.S):
            ET.fromstring(svg)
        for _, block in re.findall(r'<div class="example" data-demo="([^"]+)">.*?<pre><code[^>]*>(.*?)</code></pre>', source, re.S):
            check = subprocess.run(["bash", "-n"], input=html.unescape(re.sub('<[^>]+>', '', block)), text=True, capture_output=True)
            self.assertEqual(check.returncode, 0, check.stderr)

    def test_screenshot_provenance_and_embedded_pixels(self):
        folder = REPO / "docs/audio-ui-assets"
        manifest = json.loads((folder / "screenshots.json").read_text())
        page = self.source
        embedded = {base64.b64decode(value, validate=True) for value in re.findall(r'src="data:image/png;base64,([^"]+)"', page)}
        self.assertEqual(len(embedded), 3)
        for item in manifest["images"]:
            data = (folder / item["file"]).read_bytes()
            self.assertEqual(hashlib.sha256(data).hexdigest(), item["sha256"])
            self.assertIn(data, embedded)
            self.assertEqual(int.from_bytes(data[16:20], "big"), item["width"])
            self.assertEqual(int.from_bytes(data[20:24], "big"), item["height"])
        self.assertIn("not proof of Community fork", manifest["scope"])
        evidence = json.loads((folder / "bundle-code-map.json").read_text())
        self.assertEqual(len(evidence["entries"]), 11)
        self.assertTrue(all(e["offset"] >= 0 for e in evidence["entries"]))


class ContractTests(unittest.TestCase):
    def run_bash_python_payload(self, filename, data):
        # Выполняем именно вложенный Python из Bash. Сетевой opener заменён
        # перехватчиком: пакет до сервера не отправляется.
        script = (REPO / "scripts/voice-probes/chatgpt-transcribe.sh").read_text().split("<<'PY'\n", 1)[1].rsplit("\nPY", 1)[0]
        seen = []
        class Response(io.BytesIO):
            def __enter__(self): return self
            def __exit__(self, *args): self.close()
        class Opener:
            def open(self, request, timeout):
                seen.append(request)
                return Response(b'{"text":"synthetic transcript"}')
        with tempfile.TemporaryDirectory() as folder:
            root = Path(folder)
            path = root / filename
            path.write_bytes(data)
            auth = root / "auth.json"
            auth.write_text(json.dumps({"auth_mode": "chatgpt", "tokens": {"access_token": "[скрыто]", "account_id": "own"}}))
            output = io.StringIO()
            with patch("sys.argv", ["-", str(path), str(auth)]), patch.object(urllib.request, "build_opener", return_value=Opener()), contextlib.redirect_stdout(output):
                exec(compile(script, "chatgpt-transcribe.sh", "exec"), {"__name__": "__main__"})
            self.assertEqual(output.getvalue().strip(), "synthetic transcript")
        request = seen[0]
        mime = request.get_header("Content-type")
        envelope = BytesParser(policy=email.policy.default).parsebytes(("Content-Type: " + mime + "\r\n\r\n").encode() + request.data)
        return request, list(envelope.iter_parts())

    def test_bash_multipart_contains_exact_flac_no_metadata_or_base64(self):
        data = b"fLaC" + bytes(range(256))
        request, parts = self.run_bash_python_payload("probe.flac", data)
        self.assertEqual(request.get_method(), "POST")
        self.assertEqual(request.full_url, "https://chatgpt.com/backend-api/transcribe")
        self.assertEqual(len(parts), 1)
        self.assertEqual(parts[0].get_param("name", header="content-disposition"), "file")
        self.assertEqual(parts[0].get_content_type(), "audio/flac")
        self.assertEqual(parts[0].get_payload(decode=True), data)
        self.assertNotIn("synthetic-token", request.full_url)

    def test_mime_mismatch_rejected_before_send(self):
        # Локальная сигнатура известного контейнера; это не проверка сервера
        # и не полное декодирование повреждённых фреймов внутри контейнера.
        for bytes_ in [b"ID3-not-flac", b"RIFF-not-flac"]:
            with contextlib.redirect_stderr(io.StringIO()), self.assertRaises(SystemExit):
                self.run_bash_python_payload("wrong.flac", bytes_)

    def test_raw_pcm_rate_is_external_metadata_not_self_describing(self):
        pcm = b"\x01\x00" * 24000
        self.assertEqual(len(pcm) / (24000 * 1 * 2), 1)
        self.assertEqual(len(pcm) / (48000 * 1 * 2), .5)
        self.assertEqual(base64.b64decode(base64.b64encode(pcm), validate=True), pcm)
        with self.assertRaises(ValueError):
            base64.b64decode("!invalid", validate=True)


if __name__ == "__main__":
    unittest.main()
