#!/usr/bin/env python3
"""테스트-인터페이스 정합성 자동 감지 스크립트.

Usage:
    python3 test_interface_checker.py [--json]
"""

import argparse
import json
import re
import sys
from pathlib import Path
from typing import NamedTuple

TESTS_ROOT = Path("/home/jay/workspace/tests")
HARDCODED_PATH_RE = re.compile(r'"/home/jay/workspace/[^"\']+"|\'(/home/jay/workspace/[^"\']+)\'')
PATH_EXTRACT_RE = re.compile(r"""["'](/home/jay/workspace/[^"'\s\)]+)["']""")
SKIP_DECORATOR_RE = re.compile(r"@pytest\.mark\.skip\b")
SKIP_CALL_RE = re.compile(r"pytest\.skip\(")


class PathIssue(NamedTuple):
    file: str
    line: int
    path: str


class SkipIssue(NamedTuple):
    file: str
    line: int
    test_name: str
    reason: str


def find_test_files(root: Path) -> list[Path]:
    """테스트 루트 하위의 모든 test_*.py 파일을 반환."""
    return sorted(root.glob("test_*.py"))


def extract_path_issues(test_file: Path) -> list[PathIssue]:
    """파일에서 하드코딩된 절대 경로 추출 후 존재 여부 확인."""
    issues: list[PathIssue] = []
    lines = test_file.read_text(encoding="utf-8").splitlines()
    for lineno, line in enumerate(lines, start=1):
        for match in PATH_EXTRACT_RE.finditer(line):
            candidate = match.group(1)
            # 공백·줄바꿈 포함된 단순 접두사 제외
            if not candidate or len(candidate) <= len("/home/jay/workspace/"):
                continue
            target = Path(candidate)
            if not target.exists():
                issues.append(PathIssue(file=test_file.name, line=lineno, path=candidate))
    return issues


def extract_skip_issues(test_file: Path) -> list[SkipIssue]:
    """파일에서 영구 스킵된 테스트(데코레이터 또는 호출)를 감지."""
    issues: list[SkipIssue] = []
    lines = test_file.read_text(encoding="utf-8").splitlines()
    current_test: str = "(unknown)"
    for lineno, line in enumerate(lines, start=1):
        # 현재 테스트 함수 이름 추적
        func_match = re.match(r"\s*def (test_\w+)", line)
        if func_match:
            current_test = func_match.group(1)

        # @pytest.mark.skip 데코레이터 (영구 스킵 — reason 포함 여부 무관)
        if SKIP_DECORATOR_RE.search(line):
            reason_match = re.search(r'reason\s*=\s*["\']([^"\']*)["\']', line)
            reason = reason_match.group(1) if reason_match else "(no reason)"
            issues.append(
                SkipIssue(
                    file=test_file.name,
                    line=lineno,
                    test_name=current_test,
                    reason=reason,
                )
            )

        # pytest.skip( 직접 호출 — 조건부 스킵(if 블록 안)은 제외하지 않고 모두 수집
        elif SKIP_CALL_RE.search(line):
            reason_match = re.search(r'pytest\.skip\(\s*[f]?["\']([^"\']*)["\']', line)
            reason = reason_match.group(1) if reason_match else "(no reason)"
            issues.append(
                SkipIssue(
                    file=test_file.name,
                    line=lineno,
                    test_name=current_test,
                    reason=reason,
                )
            )
    return issues


def run_checks(
    tests_root: Path,
) -> tuple[list[PathIssue], list[SkipIssue], int]:
    """전체 점검 실행. (path_issues, skip_issues, total_files) 반환."""
    test_files = find_test_files(tests_root)
    all_path_issues: list[PathIssue] = []
    all_skip_issues: list[SkipIssue] = []

    for tf in test_files:
        all_path_issues.extend(extract_path_issues(tf))
        all_skip_issues.extend(extract_skip_issues(tf))

    return all_path_issues, all_skip_issues, len(test_files)


def determine_status(path_issues: list[PathIssue], skip_issues: list[SkipIssue]) -> str:
    if path_issues:
        return "WARN"
    if skip_issues:
        return "WARN"
    return "OK"


def print_human_report(
    path_issues: list[PathIssue],
    skip_issues: list[SkipIssue],
    total_files: int,
) -> None:
    print("=== 테스트-인터페이스 정합성 점검 ===")
    print(f"검사 파일: {total_files}개")
    print()

    if path_issues:
        print(f"[경로 불일치] {len(path_issues)}건:")
        for issue in path_issues:
            print(f"  - {issue.file}:{issue.line} → {issue.path} (NOT FOUND)")
    else:
        print("[경로 불일치] 0건: 이상 없음")

    print()

    if skip_issues:
        print(f"[영구 스킵] {len(skip_issues)}건:")
        for issue in skip_issues:
            print(f"  - {issue.file}:{issue.line} → {issue.test_name} (skip reason: {issue.reason})")
    else:
        print("[영구 스킵] 0건: 이상 없음")

    print()
    status = determine_status(path_issues, skip_issues)
    parts: list[str] = []
    if path_issues:
        parts.append(f"{len(path_issues)} path issues")
    if skip_issues:
        parts.append(f"{len(skip_issues)} permanent skips")
    detail = ", ".join(parts) if parts else "no issues"
    print(f"결과: {status} ({detail})")


def print_json_report(
    path_issues: list[PathIssue],
    skip_issues: list[SkipIssue],
    total_files: int,
) -> None:
    report = {
        "total_files": total_files,
        "status": determine_status(path_issues, skip_issues),
        "path_issues": [{"file": i.file, "line": i.line, "path": i.path} for i in path_issues],
        "skip_issues": [
            {"file": i.file, "line": i.line, "test_name": i.test_name, "reason": i.reason} for i in skip_issues
        ],
    }
    print(json.dumps(report, ensure_ascii=False, indent=2))


def main() -> None:
    parser = argparse.ArgumentParser(description="테스트-인터페이스 정합성 점검")
    parser.add_argument("--json", action="store_true", help="JSON 형식으로 출력")
    args = parser.parse_args()

    if not TESTS_ROOT.exists():
        print(f"오류: 테스트 디렉터리를 찾을 수 없습니다: {TESTS_ROOT}", file=sys.stderr)
        sys.exit(2)

    path_issues, skip_issues, total_files = run_checks(TESTS_ROOT)

    if args.json:
        print_json_report(path_issues, skip_issues, total_files)
    else:
        print_human_report(path_issues, skip_issues, total_files)

    status = determine_status(path_issues, skip_issues)
    sys.exit(0 if status == "OK" else 1)


if __name__ == "__main__":
    main()
