"""
scope_check.py - 작업 범위(scope) 검증 verifier
audit-trail.jsonl에서 실제 변경 파일을 추출하여 예상 파일 목록과 대조
"""

import json
import os

DEFAULT_AUDIT_TRAIL_PATH = "/home/jay/workspace/memory/logs/audit-trail.jsonl"


def _extract_files_from_report(task_id: str) -> list:
    """보고서의 수정 파일 섹션에서 파일 경로를 자동 추출"""
    import re

    report_path = f"/home/jay/workspace/memory/reports/{task_id}.md"
    try:
        with open(report_path, "r", encoding="utf-8") as f:
            content = f.read()
    except (FileNotFoundError, OSError):
        return []

    files = []
    # 테이블 행 파싱: | path/to/file.py:123 | ... |
    for match in re.finditer(r"\|\s*([\w/._-]+\.(?:py|ts|tsx|js|jsx|sh|md|json|yaml|yml))(?::\d+)?\s*\|", content):
        files.append(match.group(1))
    # 리스트 항목 파싱: - path/to/file.py 또는 - `/path/to/file.py`
    for match in re.finditer(
        r"^[-*]\s+`?(/home/jay/workspace/)?([\w/._-]+\.(?:py|ts|tsx|js|jsx|sh|md|json|yaml|yml))(?::\d+)?`?",
        content,
        re.MULTILINE,
    ):
        path = match.group(2)
        if path not in files:
            files.append(path)
    return files


def verify(expected_files: list, task_id: str, audit_trail_path: str = "") -> dict:
    """
    audit-trail.jsonl에서 task_id에 해당하는 실제 변경 파일을 추출하고,
    expected_files와 대조하여 범위 이탈 여부를 검증합니다.

    Args:
        expected_files: 변경이 허용된 파일 경로 목록
        task_id: 검사 대상 task ID
        audit_trail_path: audit-trail.jsonl 파일 경로
                          (기본값: /home/jay/workspace/memory/logs/audit-trail.jsonl)

    Returns:
        {"status": "PASS"|"WARN"|"SKIP", "details": [...]}
    """
    if not expected_files:
        # 보고서에서 수정 파일 자동 추출 시도 (task-1994)
        expected_files = _extract_files_from_report(task_id)
        if not expected_files:
            return {
                "status": "SKIP",
                "details": ["No expected files specified and report extraction failed — scope check skipped"],
            }

    trail_path = audit_trail_path if audit_trail_path else DEFAULT_AUDIT_TRAIL_PATH

    # audit-trail.jsonl 읽기
    try:
        with open(trail_path, "r", encoding="utf-8") as f:
            lines = f.readlines()
    except FileNotFoundError:
        return {
            "status": "WARN",
            "details": [f"audit-trail 부재로 범위 검증 불가: {trail_path}"],
        }
    except OSError as e:
        return {
            "status": "WARN",
            "details": [f"audit-trail 읽기 실패로 범위 검증 불가: {type(e).__name__}: {e}"],
        }

    # task_id에 해당하는 변경 파일 수집
    actual_files: set = set()
    parse_errors = 0

    for line in lines:
        line = line.strip()
        if not line:
            continue
        try:
            entry = json.loads(line)
        except json.JSONDecodeError:
            parse_errors += 1
            continue

        if entry.get("task_id") != task_id:
            continue

        # 변경 파일 필드: "files", "changed_files", "file" 중 존재하는 것을 수집
        for key in ("files", "changed_files"):
            value = entry.get(key)
            if isinstance(value, list):
                for f in value:
                    if isinstance(f, str):
                        actual_files.add(os.path.normpath(f))
            elif isinstance(value, str):
                actual_files.add(os.path.normpath(value))

        single = entry.get("file")
        if isinstance(single, str):
            actual_files.add(os.path.normpath(single))

    if not actual_files:
        details = [f"No changed files found in audit-trail for task_id='{task_id}'"]
        if parse_errors:
            details.append(f"({parse_errors} line(s) failed to parse as JSON)")
        return {"status": "SKIP", "details": details}

    # 정규화된 expected 집합
    normalized_expected: set = {os.path.normpath(f) for f in expected_files}

    # 예상 외 변경 파일 탐색
    unexpected = sorted(actual_files - normalized_expected)

    details = [
        f"Expected: {len(normalized_expected)} files",
        f"Actual: {len(actual_files)} files",
    ]

    if parse_errors:
        details.append(f"({parse_errors} line(s) failed to parse as JSON)")

    if not unexpected:
        return {"status": "PASS", "details": details}

    for path in unexpected:
        details.append(f"Unexpected: {path}")

    return {"status": "WARN", "details": details}


if __name__ == "__main__":
    import sys

    # 사용법: scope_check.py <task_id> [audit_trail_path] -- <expected_file1> <expected_file2> ...
    # 예시:   scope_check.py TASK-42 -- src/main.py src/utils.py
    #         scope_check.py TASK-42 /path/to/audit-trail.jsonl -- src/main.py
    args = sys.argv[1:]

    if not args:
        print(
            json.dumps(
                {"status": "SKIP", "details": ["Usage: scope_check.py <task_id> [audit_trail_path] -- <file> ..."]},
                ensure_ascii=False,
                indent=2,
            )
        )
        sys.exit(0)

    _task_id = args[0]
    _audit_trail_path = ""

    # '--' 구분자 기준으로 expected_files 분리
    if "--" in args:
        sep_idx = args.index("--")
        _middle = args[1:sep_idx]
        _expected = args[sep_idx + 1 :]
        if _middle:
            _audit_trail_path = _middle[0]
    else:
        _expected = args[1:]

    result = verify(_expected, _task_id, _audit_trail_path)
    print(json.dumps(result, ensure_ascii=False, indent=2))
