#!/usr/bin/env python3
"""Scan Unreal/client/server logs for critical 30-minute-soak failures."""

from __future__ import annotations

import argparse
import re
import sys
from pathlib import Path


CRITICAL_PATTERNS = [
    re.compile(pattern, re.IGNORECASE)
    for pattern in [
        r"\bFatal\b",
        r"\bCritical\b",
        r"\bCrash\b",
        r"\bAssert(?:ion)?\b",
        r"\bEnsure condition failed\b",
        r"\bUnhandled Exception\b",
        r"\bAccess violation\b",
        r"\bLogOutputDevice:\s*Error\b",
        r"\bLogWindows:\s*Error\b",
        r"\bLogLinux:\s*Error\b",
        r"\bCallstack\b",
    ]
]

# Keep this narrow. Add entries only for known benign engine noise with a commit
# note explaining why the line is allowed.
ALLOWLIST_PATTERNS = [
    re.compile(r"LogWindows: Failed to load 'aqProf.dll'", re.IGNORECASE),
    re.compile(r"LogWindows: Failed to load 'VtuneApi\.dll'", re.IGNORECASE),
    re.compile(r"LogInit:\s+-crashhandlerstacksize\b", re.IGNORECASE),
    re.compile(r"LogInit:\s+-allowsyscallfilterfile=.*will\*? cause a crash", re.IGNORECASE),
]

DEFAULT_LOG_SUFFIXES = {".log", ".txt"}


def iter_log_files(paths: list[Path]) -> list[Path]:
    files: list[Path] = []
    for path in paths:
        if path.is_dir():
            files.extend(
                candidate
                for candidate in path.rglob("*")
                if candidate.is_file() and candidate.suffix.lower() in DEFAULT_LOG_SUFFIXES
            )
        elif path.is_file():
            files.append(path)
        else:
            raise FileNotFoundError(f"Log path does not exist: {path}")
    return sorted(set(files))


def is_allowed(line: str) -> bool:
    return any(pattern.search(line) for pattern in ALLOWLIST_PATTERNS)


def is_critical(line: str) -> bool:
    return any(pattern.search(line) for pattern in CRITICAL_PATTERNS) and not is_allowed(line)


def scan_file(path: Path) -> list[tuple[int, str]]:
    matches: list[tuple[int, str]] = []
    with path.open("r", encoding="utf-8", errors="replace") as handle:
        for line_number, line in enumerate(handle, start=1):
            if is_critical(line):
                matches.append((line_number, line.rstrip()))
    return matches


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("paths", nargs="+", type=Path, help="Log files or directories to scan.")
    args = parser.parse_args()

    log_files = iter_log_files(args.paths)
    if not log_files:
        print("ERROR: no .log or .txt files found to scan.", file=sys.stderr)
        return 2

    failures: list[str] = []
    for log_file in log_files:
        matches = scan_file(log_file)
        for line_number, line in matches:
            failures.append(f"{log_file}:{line_number}: {line}")

    if failures:
        print("FAILED: critical log spam detected.")
        for failure in failures[:200]:
            print(failure)
        if len(failures) > 200:
            print(f"... {len(failures) - 200} additional matches suppressed")
        return 1

    print(f"OK: scanned {len(log_files)} log file(s); no critical log spam detected.")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
