#!/usr/bin/env python3
"""Validate GDR-8050L E7/HL parent fallback trace logs.

This is intentionally log-level validation: it does not re-emulate the firmware.
It checks that the trace reached the no-skip v35/v36 baseline:
  * four queue submissions in the expected order
  * no call_policy skip events
  * no generic/unimplemented extension placeholders on the exercised path
  * real execution of the former edge helpers 0x900210C4, 0x90021304, 0x9001BD2B
  * real decoding of F2 D0 / F2 64 in 0x90021304

Usage:
  python validate_gdr8050l_e7_hl_trace.py run1.output.txt [run2.output.txt ...]
"""
from __future__ import annotations

import re
import sys
from pathlib import Path
from typing import Iterable

EXPECTED_QUEUES = [
    ("0x5D", "3", "0000A9"),
    ("0x59", "4", "00000000"),
    ("0x51", "14", "02000001000000000000B0CB0300"),
    ("0x25", "16", "010100000000F0FFFF00F0FFFF000000"),
]

FORBIDDEN_TOKENS = [
    "[F2 EXTENSION]",
    "[F3 EXTENSION]",
    "[F4 EXTENSION]",
    "[F5 EXTENSION]",
    "[F8 EXTENSION]",
    "[FA EXTENSION]",
    "[FC EXTENSION]",
    "[FE EXTENSION]",
    "[GENERIC",
    "[-] FATAL",
]

REQUIRED_TOKENS = [
    "0x900210C4: 34 04 D9    movbu (0xD904), d0",
    "0x90021304: FE 81 5C D9 60 bset/btst",
    "0x9001BD2B: 03 4C 06    movhu d0, (0x064C)",
    "0x90021335: F2 D0          ext d0",
    "0x90021337: F2 64          div d1, d0",
    "0x90028640: DF B0 20    ret [d2,a2,a3],32 (HALT: no return target)",
]

QUEUE_RE = re.compile(
    r"^\[QUEUE\] .* opcode=(0x[0-9A-F]{2}); len=(\d+)(?:;[^\n]*)? payload=([0-9A-F]+)$",
    re.MULTILINE,
)
SUMMARY_RE = re.compile(r"^\[\*\] Trace event summary: (?P<summary>.+)$", re.MULTILINE)


def _read_text(path: Path) -> str:
    raw = path.read_bytes()
    for enc in ("utf-8-sig", "utf-16", "utf-16-le"):
        try:
            return raw.decode(enc).replace("\r\n", "\n").replace("\r", "\n")
        except UnicodeDecodeError:
            pass
    return raw.decode("utf-8", errors="replace").replace("\r\n", "\n").replace("\r", "\n")


def _summary_value(summary: str, key: str) -> int | None:
    m = re.search(rf"(?:^|, )" + re.escape(key) + r"=(\d+)(?:,|$)", summary)
    return int(m.group(1)) if m else None


def _split_runs(text: str) -> list[str]:
    starts = [m.start() for m in re.finditer(r"^={20,}\n\s*MN103S Emulator", text, re.MULTILINE)]
    if len(starts) <= 1:
        return [text]
    starts.append(len(text))
    return [text[starts[i]:starts[i + 1]] for i in range(len(starts) - 1)]


def validate_run(text: str, run_index: int = 1) -> list[str]:
    prefix = f"run {run_index}: "
    errors: list[str] = []

    queues = QUEUE_RE.findall(text)
    if queues != EXPECTED_QUEUES:
        errors.append(
            prefix
            + "queue sequence mismatch: expected "
            + ", ".join(f"{op}/{length}/{payload}" for op, length, payload in EXPECTED_QUEUES)
            + "; got "
            + ", ".join(f"{op}/{length}/{payload}" for op, length, payload in queues)
        )

    summaries = [m.group("summary") for m in SUMMARY_RE.finditer(text)]
    if not summaries:
        errors.append(prefix + "missing trace event summary")
    else:
        summary = summaries[-1]
        if _summary_value(summary, "queue_submit") != 4:
            errors.append(prefix + f"summary queue_submit is not 4: {summary}")
        if "call_skip=" in summary:
            errors.append(prefix + f"summary still contains call_skip: {summary}")

    if "[SKIPPED]" in text and "call_skip" in text:
        errors.append(prefix + "log contains call_skip text")

    for token in FORBIDDEN_TOKENS:
        if token in text:
            errors.append(prefix + f"forbidden token present: {token}")

    for token in REQUIRED_TOKENS:
        if token not in text:
            errors.append(prefix + f"required token missing: {token}")

    if "Execution reached return with no target" not in text:
        errors.append(prefix + "missing safe top-level no-return halt")

    return errors


def validate_log(path: Path) -> list[str]:
    text = _read_text(path)
    errors: list[str] = []
    for i, run_text in enumerate(_split_runs(text), 1):
        errors.extend(validate_run(run_text, i))
    return errors


def main(argv: Iterable[str]) -> int:
    paths = [Path(arg) for arg in argv]
    if not paths:
        print("usage: validate_gdr8050l_e7_hl_trace.py LOG [LOG ...]", file=sys.stderr)
        return 2

    failed = False
    for path in paths:
        if not path.exists():
            print(f"[FAIL] {path}: file not found")
            failed = True
            continue
        errors = validate_log(path)
        if errors:
            print(f"[FAIL] {path}")
            for err in errors:
                print(f"  - {err}")
            failed = True
        else:
            print(f"[OK] {path}: expected no-call-skip E7/HL trace baseline")
    return 1 if failed else 0


if __name__ == "__main__":
    raise SystemExit(main(sys.argv[1:]))
