#!/usr/bin/env python3
"""
QA Validator — phát hiện các lỗi cấu trúc mà pipeline cũ bỏ sót.

Bắt 2 class lỗi chính:
  A. Final SRT (Myanmar + Việt):
     1. Cue duration bất thường (>= MAX_DUR_S, mặc định 20s) — "cue chiếm cả chunk"
     2. Timestamp không đơn điệu tăng / end <= start
     3. Gap giữa 2 cue > MAX_GAP_S (mặc định 4s) — nghi sót nội dung
     4. Cue quá ngắn < MIN_DUR_MS (mặc định 300ms)
  B. Chunk transcript thô (_gemini_raw.txt):
     5. Chunk có <= 2 dòng timestamp — Gemini collapse
     6. Repetition loop: 1 từ lặp >= LOOP_N lần liên tiếp

READ-ONLY: không ghi/sửa bất kỳ file nào. Exit code = số lỗi nghiêm trọng (A1/A2/B5/B6).

Usage:
    python3 qa_validate_srt.py
    python3 qa_validate_srt.py --max-dur 20 --max-gap 4 --loop 20
"""
import argparse
import glob
import re
import os
import sys

PROJ = "/home/tuan-nguyen/.openclaw/workspace/015-phu_de_video"
TS_RE = re.compile(
    r'^(\d{1,2}):(\d{2}):(\d{2}),(\d{3})\s*-->\s*(\d{1,2}):(\d{2}):(\d{2}),(\d{3})$'
)


def to_ms(t):
    h, m, s, ms = [int(x) for x in re.split(r'[:,]', t)]
    return h * 3600000 + m * 60000 + s * 1000 + ms


def fmt_ms(ms):
    h = ms // 3600000; ms %= 3600000
    m = ms // 60000; ms %= 60000
    s = ms // 1000; ms %= 1000
    return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"


def scan_srt(path, cfg):
    """Trả về list [(severity, msg)] — severity 2=critical, 1=warning."""
    issues = []
    try:
        blocks = open(path, encoding="utf-8").read().split("\n\n")
    except OSError as e:
        return [(2, f"không đọc được: {e}")]

    segs = []
    for blk in blocks:
        lines = blk.split("\n")
        if len(lines) < 3:
            continue
        m = TS_RE.match(lines[1].strip())
        if not m:
            continue
        idx = lines[0].strip()
        s = to_ms(f"{m.group(1)}:{m.group(2)}:{m.group(3)}:{m.group(4)}")
        e = to_ms(f"{m.group(5)}:{m.group(6)}:{m.group(7)}:{m.group(8)}")
        segs.append((idx, s, e))

    n = len(segs)
    for i, (idx, s, e) in enumerate(segs):
        dur = e - s
        if dur <= 0:
            issues.append((2, f"cue {idx}: end <= start ({fmt_ms(s)} --> {fmt_ms(e)})"))
        if dur >= cfg.max_dur * 1000:
            issues.append((2, f"cue {idx}: duration {dur/1000:.1f}s >= {cfg.max_dur}s "
                              f"({fmt_ms(s)} --> {fmt_ms(e)}) — nghi 'chiếm cả chunk'"))
        if dur < cfg.min_dur_ms:
            issues.append((1, f"cue {idx}: quá ngắn {dur}ms"))
        if i > 0 and s < segs[i - 1][2]:
            issues.append((2, f"cue {idx}: start lùi sau cue {segs[i-1][0]} "
                              f"(timestamp không đơn điệu)"))
        if i > 0:
            gap = s - segs[i - 1][2]
            if gap > cfg.max_gap * 1000:
                issues.append((1, f"sau cue {segs[i-1][0]}: gap {gap/1000:.1f}s "
                                  f"> {cfg.max_gap}s — nghi sót nội dung"))

    return issues


def scan_chunks(pattern, cfg):
    issues = []
    for f in sorted(glob.glob(pattern)):
        m = re.search(r'chunk_(\d+)', f)
        cid = m.group(1) if m else os.path.basename(f)
        try:
            txt = open(f, encoding="utf-8").read()
        except OSError:
            continue
        lines = [l for l in txt.splitlines() if l.strip()]
        if len(lines) <= 2:
            issues.append((2, f"{os.path.basename(f)}: chỉ {len(lines)} dòng timestamp "
                              f"— Gemini collapse"))
        # repetition loop
        toks = txt.split()
        maxrun, runword, cur = 0, None, 1
        for i in range(1, len(toks)):
            if toks[i] == toks[i - 1]:
                cur += 1
            else:
                if cur > maxrun:
                    maxrun, runword = cur, toks[i - 1]
                cur = 1
        if cur > maxrun:
            maxrun, runword = cur, toks[-1]
        if maxrun >= cfg.loop:
            issues.append((2, f"{os.path.basename(f)}: repetition loop '{runword}' "
                              f"x{maxrun} lần"))
    return issues


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--max-dur", type=float, default=20.0, help="ngưỡng duration (s)")
    ap.add_argument("--max-gap", type=float, default=4.0, help="ngưỡng gap (s)")
    ap.add_argument("--loop", type=int, default=20, help="ngưỡng repetition loop (lần)")
    ap.add_argument("--min-dur-ms", type=int, default=300)
    args = ap.parse_args()

    all_issues = []
    for p in ("001", "002"):
        for name in (f"{p}_final.srt", f"{p}_final_vi.srt"):
            path = os.path.join(PROJ, p, name)
            if not os.path.exists(path):
                continue
            for sev, msg in scan_srt(path, args):
                all_issues.append((sev, f"[{name}] {msg}"))

    for p in ("001", "002"):
        pattern = os.path.join(PROJ, p, "transcripts", "*_gemini_raw.txt")
        for sev, msg in scan_chunks(pattern, args):
            all_issues.append((sev, f"[chunks {p}] {msg}"))

    if not all_issues:
        print("✅ PASS — không phát hiện lỗi cấu trúc nào.")
        sys.exit(0)

    crit = [m for s, m in all_issues if s == 2]
    warn = [m for s, m in all_issues if s == 1]
    print(f"❌ FAIL — {len(crit)} critical, {len(warn)} warning\n")
    for m in crit:
        print(f"  [CRIT] {m}")
    for m in warn:
        print(f"  [WARN] {m}")
    sys.exit(len(crit))


if __name__ == "__main__":
    main()
