#!/usr/bin/env python3
"""
cross_model_review.py - 크로스모델 코드 리뷰 트리거

critical/security 레벨 작업 완료 시 비너스(Gemini) 또는 아틀라스(GPT)에
독립 코드 리뷰를 요청하고, 발견 사항을 교차 분석한다.

출처: gstack /codex 패턴 (MIT 라이선스, https://github.com/garrytan/gstack)
3가지 모드: Review, Challenge, Consult

Usage:
    python3 cross_model_review.py --task-id task-xxx --mode review
    python3 cross_model_review.py --task-id task-xxx --mode challenge
    python3 cross_model_review.py --task-id task-xxx --mode consult --question "..."
"""

import argparse
import json
import os
import sys
from datetime import datetime
from pathlib import Path
from typing import Optional

WORKSPACE = Path(os.environ.get("WORKSPACE_ROOT", "/home/jay/workspace"))
REPORTS_DIR = WORKSPACE / "memory" / "reports"
EVENTS_DIR = WORKSPACE / "memory" / "events"
DECISIONS_DIR = WORKSPACE / "memory" / "events" / "decisions"


def _load_report(task_id: str) -> Optional[str]:
    """task_id에 해당하는 보고서 내용 로드."""
    report_path = REPORTS_DIR / f"{task_id}.md"
    if not report_path.exists():
        return None
    return report_path.read_text(encoding="utf-8")


def _load_done(task_id: str) -> Optional[dict]:  # type: ignore[type-arg]
    """.done 파일 로드."""
    done_path = EVENTS_DIR / f"{task_id}.done"
    if not done_path.exists():
        return None
    try:
        return json.loads(done_path.read_text(encoding="utf-8"))  # type: ignore[no-any-return]
    except (json.JSONDecodeError, OSError):
        return None


def _get_changed_files(task_id: str) -> list[str]:
    """보고서에서 변경 파일 목록 추출."""
    import re
    report = _load_report(task_id)
    if not report:
        return []
    paths = re.findall(r"(/home/jay/workspace/\S+\.(?:py|js|ts|tsx|html))", report)
    return [p for p in paths if os.path.isfile(p)]


def _record_decision(task_id: str, stage: str, decision: str, **extra: object) -> None:
    """JSONL 결정 히스토리에 기록."""
    DECISIONS_DIR.mkdir(parents=True, exist_ok=True)
    entry = {
        "task_id": task_id,
        "stage": stage,
        "decision": decision,
        "timestamp": datetime.now().isoformat(),
        **extra,
    }
    decisions_file = DECISIONS_DIR / f"{task_id}.jsonl"
    with open(decisions_file, "a", encoding="utf-8") as f:
        f.write(json.dumps(entry, ensure_ascii=False) + "\n")


def build_review_prompt(task_id: str, mode: str, question: str = "") -> str:
    """크로스모델 리뷰 프롬프트 생성."""
    report = _load_report(task_id)
    changed_files = _get_changed_files(task_id)

    base_prompt = f"""# 크로스모델 코드 리뷰 요청

## Task: {task_id}
## 모드: {mode}
## 보고서 요약:
{report[:2000] if report else '보고서 없음'}

## 변경 파일 ({len(changed_files)}개):
"""
    for f in changed_files[:20]:
        base_prompt += f"- {f}\n"

    if mode == "review":
        base_prompt += """
## 리뷰 지시사항
위 작업의 코드 변경을 독립적으로 리뷰하세요.

### Pass 1 (CRITICAL — 발견 시 FAIL):
- SQL & Data Safety: 위험한 쿼리 패턴
- Race Conditions: 동시 접근 위험
- LLM Output Trust Boundary: AI 출력 미검증 사용
- Security: 인젝션, 인증 우회, 비밀키 노출

### Pass 2 (INFORMATIONAL — 경고만):
- Magic Numbers, Dead Code, Test Gaps, Performance

각 발견 사항에 대해:
- 파일:줄번호
- 카테고리 (CRITICAL/INFORMATIONAL)
- 설명
- 수정 제안
"""
    elif mode == "challenge":
        base_prompt += """
## 챌린지 모드 지시사항
적대적 관점에서 코드를 검토하세요. 코드를 깨뜨리려는 시도를 하세요.

1. 입력 경계값에서 깨지는 시나리오
2. 동시성 문제
3. 에러 핸들링 누락
4. 보안 취약점
5. 성능 병목
"""
    elif mode == "consult":
        base_prompt += f"""
## 컨설팅 모드 지시사항
아래 질문에 대해 코드 맥락을 참고하여 답변하세요:

질문: {question}
"""

    return base_prompt


def generate_review_request(
    task_id: str,
    mode: str = "review",
    question: str = "",
) -> dict:  # type: ignore[type-arg]
    """크로스모델 리뷰 요청 생성 (실제 API 호출은 하지 않음, 프롬프트만 생성)."""

    report = _load_report(task_id)
    if not report:
        return {
            "status": "error",
            "message": f"보고서 없음: {REPORTS_DIR / f'{task_id}.md'}",
        }

    done = _load_done(task_id)
    if not done:
        return {
            "status": "error",
            "message": f".done 파일 없음: {task_id}. 작업 완료 후 리뷰 요청하세요.",
        }

    prompt = build_review_prompt(task_id, mode, question)

    # 프롬프트를 파일로 저장
    output_dir = WORKSPACE / "memory" / "cross-reviews"
    output_dir.mkdir(parents=True, exist_ok=True)
    output_file = output_dir / f"{task_id}-{mode}-{datetime.now().strftime('%Y%m%d%H%M%S')}.md"
    output_file.write_text(prompt, encoding="utf-8")

    # 결정 기록
    _record_decision(
        task_id,
        stage="cross_review",
        decision=f"Cross-model review requested: mode={mode}",
        mode=mode,
        output_file=str(output_file),
    )

    return {
        "status": "ok",
        "task_id": task_id,
        "mode": mode,
        "prompt_file": str(output_file),
        "prompt_length": len(prompt),
        "changed_files": len(_get_changed_files(task_id)),
        "message": (
            f"크로스모델 리뷰 프롬프트 생성 완료.\n"
            f"프롬프트 파일: {output_file}\n"
            f"비너스(Gemini) 또는 아틀라스(GPT)에 이 프롬프트를 전달하세요.\n"
            f"dispatch.py --team dev3-team --task-file {output_file} 로 위임 가능."
        ),
    }


def analyze_cross_findings(
    task_id: str,
    claude_findings: list[str],
    other_findings: list[str],
) -> dict:  # type: ignore[type-arg]
    """두 엔진의 발견 사항 교차 분석."""
    claude_set = set(claude_findings)
    other_set = set(other_findings)

    both = claude_set & other_set
    only_claude = claude_set - other_set
    only_other = other_set - claude_set

    total = len(claude_set | other_set)
    agreement = len(both) / total * 100 if total > 0 else 100.0

    result: dict[str, object] = {
        "task_id": task_id,
        "agreement_rate": round(agreement, 1),
        "both_found": list(both),
        "only_claude": list(only_claude),
        "only_other": list(only_other),
        "total_unique": total,
    }

    # 결정 기록
    _record_decision(
        task_id,
        stage="cross_review_analysis",
        decision=f"Agreement rate: {agreement:.1f}%",
        findings=total,
    )

    return result


def main() -> None:
    parser = argparse.ArgumentParser(description="크로스모델 코드 리뷰")
    parser.add_argument("--task-id", required=True, help="리뷰할 task ID")
    parser.add_argument(
        "--mode",
        default="review",
        choices=["review", "challenge", "consult"],
        help="리뷰 모드 (review/challenge/consult)",
    )
    parser.add_argument("--question", default="", help="consult 모드 질문")

    args = parser.parse_args()

    result = generate_review_request(
        task_id=args.task_id,
        mode=args.mode,
        question=args.question,
    )

    print(json.dumps(result, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()
