"""
통합 테스트: 3-Layer Graduated Auto-Gate
task-1837_5.2 - 벨레스(테스터) 작성

대상 클래스:
  - BatchWatchdog (L1): .done 파일 스캔 → batch_id 전팀 완료 감지 → TTL 경고
  - PreFlightCheck (L2): git merge --no-commit 시뮬레이션 → 충돌 검증
  - IntegrationTestRunner (L3): pytest tests/integration/ 실행
  - GraduatedAutoGate: L1→L2→L3 오케스트레이터
"""

import json
import subprocess
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path
from unittest.mock import patch

sys.path.insert(0, "/home/jay/workspace")

from scripts.auto_merge import (  # noqa: E402  # pyright: ignore[reportMissingImports]
    BatchWatchdog,
    GraduatedAutoGate,
    IntegrationTestRunner,
    PreFlightCheck,
)

# ---------------------------------------------------------------------------
# 헬퍼
# ---------------------------------------------------------------------------


def _make_events_dir(tmp_path: Path) -> Path:
    """tmp_path/memory/events 디렉터리 생성."""
    d = tmp_path / "memory" / "events"
    d.mkdir(parents=True, exist_ok=True)
    return d


def _make_timers(tmp_path: Path, tasks: dict) -> Path:
    """tmp_path/memory/task-timers.json 생성."""
    memory = tmp_path / "memory"
    memory.mkdir(parents=True, exist_ok=True)
    timer_file = memory / "task-timers.json"
    timer_file.write_text(json.dumps({"tasks": tasks}), encoding="utf-8")
    return timer_file


# ---------------------------------------------------------------------------
# L1 - BatchWatchdog 테스트
# ---------------------------------------------------------------------------


def test_batch_watchdog_scan_done_files(tmp_path):
    """batch_id가 있는 .done 파일만 수집해야 한다."""
    events = _make_events_dir(tmp_path)

    # batch_id 있는 .done 파일 2개
    (events / "task-1.done").write_text(
        json.dumps({"task_id": "task-1", "batch_id": "batch-A", "status": "done"}),
        encoding="utf-8",
    )
    (events / "task-2.done").write_text(
        json.dumps({"task_id": "task-2", "batch_id": "batch-A", "status": "done"}),
        encoding="utf-8",
    )

    # batch_id 없는 .done 파일
    (events / "task-3.done").write_text(
        json.dumps({"task_id": "task-3", "status": "done"}),
        encoding="utf-8",
    )

    # 이미 처리된 파일 (.done.clear 동반)
    (events / "task-4.done").write_text(
        json.dumps({"task_id": "task-4", "batch_id": "batch-A", "status": "done"}),
        encoding="utf-8",
    )
    (events / "task-4.done.clear").touch()

    watchdog = BatchWatchdog(workspace_path=str(tmp_path))
    result = watchdog.scan_done_files()

    # batch_id 있는 2개만 반환 (task-4는 .done.clear 때문에 제외)
    assert len(result) == 2
    task_ids = {r["task_id"] for r in result}
    assert task_ids == {"task-1", "task-2"}


def test_batch_watchdog_check_batch_completion_all_done(tmp_path):
    """batch_id의 모든 task가 done이면 complete=True."""
    _make_timers(
        tmp_path,
        {
            "task-1": {"batch_id": "batch-A", "status": "done"},
            "task-2": {"batch_id": "batch-A", "status": "done"},
            "task-3": {"batch_id": "batch-A", "status": "done"},
        },
    )

    watchdog = BatchWatchdog(workspace_path=str(tmp_path))
    result = watchdog.check_batch_completion("batch-A")

    assert result["complete"] is True
    assert result["total"] == 3
    assert result["done"] == 3
    assert result["pending"] == []


def test_batch_watchdog_check_batch_completion_partial(tmp_path):
    """일부 running이면 complete=False, pending에 포함."""
    _make_timers(
        tmp_path,
        {
            "task-1": {"batch_id": "batch-A", "status": "done"},
            "task-2": {"batch_id": "batch-A", "status": "done"},
            "task-3": {"batch_id": "batch-A", "status": "running"},
        },
    )

    watchdog = BatchWatchdog(workspace_path=str(tmp_path))
    result = watchdog.check_batch_completion("batch-A")

    assert result["complete"] is False
    assert result["total"] == 3
    assert result["done"] == 2
    assert "task-3" in result["pending"]


def test_batch_watchdog_check_ttl_expired(tmp_path):
    """start_time이 2시간 이상인 running task를 감지해야 한다."""
    # 3시간 전 시각 (UTC ISO 형식)
    three_hours_ago = (datetime.now(timezone.utc) - timedelta(hours=3)).isoformat()

    _make_timers(
        tmp_path,
        {
            "task-old": {
                "batch_id": "batch-A",
                "status": "running",
                "start_time": three_hours_ago,
            },
        },
    )

    watchdog = BatchWatchdog(workspace_path=str(tmp_path))
    stale = watchdog.check_ttl("batch-A", ttl_hours=2.0)

    assert len(stale) == 1
    assert stale[0]["task_id"] == "task-old"
    assert stale[0]["elapsed_hours"] > 2.0


def test_batch_watchdog_run_returns_completed_batches(tmp_path):
    """전팀 완료 batch_id만 반환해야 한다."""
    events = _make_events_dir(tmp_path)

    # batch-A: 완료 (2개 task)
    (events / "task-a1.done").write_text(
        json.dumps({"task_id": "task-a1", "batch_id": "batch-A", "status": "done"}),
        encoding="utf-8",
    )
    (events / "task-a2.done").write_text(
        json.dumps({"task_id": "task-a2", "batch_id": "batch-A", "status": "done"}),
        encoding="utf-8",
    )

    # batch-B: 미완료 (1개 running)
    (events / "task-b1.done").write_text(
        json.dumps({"task_id": "task-b1", "batch_id": "batch-B", "status": "done"}),
        encoding="utf-8",
    )

    _make_timers(
        tmp_path,
        {
            "task-a1": {"batch_id": "batch-A", "status": "done"},
            "task-a2": {"batch_id": "batch-A", "status": "done"},
            "task-b1": {"batch_id": "batch-B", "status": "done"},
            "task-b2": {"batch_id": "batch-B", "status": "running"},
        },
    )

    watchdog = BatchWatchdog(workspace_path=str(tmp_path))
    # send_ttl_warning은 mock (실제 전송 방지)
    with patch.object(watchdog, "send_ttl_warning"):
        completed = watchdog.run()

    # batch-A만 완료
    assert "batch-A" in completed
    assert "batch-B" not in completed


# ---------------------------------------------------------------------------
# L2 - PreFlightCheck 테스트
# ---------------------------------------------------------------------------


def test_preflight_find_merge_branches(tmp_path):
    """batch_id에 해당하는 task의 merge_branch를 수집해야 한다."""
    events = _make_events_dir(tmp_path)

    (events / "task-1.done").write_text(
        json.dumps(
            {
                "task_id": "task-1",
                "batch_id": "batch-A",
                "status": "done",
                "merge_branch": "task/task-1-dev1",
                "project_path": "/some/project",
            }
        ),
        encoding="utf-8",
    )
    (events / "task-2.done").write_text(
        json.dumps(
            {
                "task_id": "task-2",
                "batch_id": "batch-A",
                "status": "done",
                "merge_branch": "task/task-2-dev2",
                "project_path": "/some/project",
            }
        ),
        encoding="utf-8",
    )
    # batch-B는 포함되면 안 됨
    (events / "task-3.done").write_text(
        json.dumps(
            {
                "task_id": "task-3",
                "batch_id": "batch-B",
                "status": "done",
                "merge_branch": "task/task-3-dev3",
            }
        ),
        encoding="utf-8",
    )

    preflight = PreFlightCheck(workspace_path=str(tmp_path))
    branches = preflight.find_merge_branches("batch-A")

    assert len(branches) == 2
    branch_names = {b["branch"] for b in branches}
    assert branch_names == {"task/task-1-dev1", "task/task-2-dev2"}


def test_preflight_simulate_merge_no_conflict(tmp_path):
    """충돌 없는 경우 passed=True를 반환해야 한다."""
    repo = tmp_path / "repo"
    repo.mkdir()

    # git repo 초기화
    subprocess.run(["git", "init"], cwd=str(repo), capture_output=True, check=True)
    subprocess.run(
        ["git", "config", "user.email", "test@test.com"],
        cwd=str(repo),
        capture_output=True,
        check=True,
    )
    subprocess.run(
        ["git", "config", "user.name", "Test"],
        cwd=str(repo),
        capture_output=True,
        check=True,
    )

    # main 브랜치: 베이스 커밋
    (repo / "file_a.txt").write_text("line1\n", encoding="utf-8")
    subprocess.run(["git", "add", "."], cwd=str(repo), capture_output=True, check=True)
    subprocess.run(
        ["git", "commit", "-m", "init"],
        cwd=str(repo),
        capture_output=True,
        check=True,
    )

    # feature-1: 다른 파일 수정 (충돌 없음)
    subprocess.run(
        ["git", "checkout", "-b", "feature-1"],
        cwd=str(repo),
        capture_output=True,
        check=True,
    )
    (repo / "file_b.txt").write_text("feature1 content\n", encoding="utf-8")
    subprocess.run(["git", "add", "."], cwd=str(repo), capture_output=True, check=True)
    subprocess.run(
        ["git", "commit", "-m", "feature-1"],
        cwd=str(repo),
        capture_output=True,
        check=True,
    )

    # main으로 돌아오기
    subprocess.run(
        ["git", "checkout", "master"],
        cwd=str(repo),
        capture_output=True,
    )
    subprocess.run(
        ["git", "checkout", "main"],
        cwd=str(repo),
        capture_output=True,
    )

    preflight = PreFlightCheck(workspace_path=str(tmp_path))
    result = preflight.simulate_merge(str(repo), ["feature-1"])

    assert result["passed"] is True
    assert result["conflicts"] == []


def test_preflight_simulate_merge_with_conflict(tmp_path):
    """충돌 시 passed=False + conflict 파일 목록을 반환해야 한다."""
    repo = tmp_path / "repo"
    repo.mkdir()

    # git repo 초기화
    subprocess.run(["git", "init"], cwd=str(repo), capture_output=True, check=True)
    subprocess.run(
        ["git", "config", "user.email", "test@test.com"],
        cwd=str(repo),
        capture_output=True,
        check=True,
    )
    subprocess.run(
        ["git", "config", "user.name", "Test"],
        cwd=str(repo),
        capture_output=True,
        check=True,
    )

    # main 브랜치: 베이스 커밋 (같은 파일)
    (repo / "conflict.txt").write_text("base content\n", encoding="utf-8")
    subprocess.run(["git", "add", "."], cwd=str(repo), capture_output=True, check=True)
    subprocess.run(
        ["git", "commit", "-m", "init"],
        cwd=str(repo),
        capture_output=True,
        check=True,
    )

    # main에서 같은 파일 수정 (conflict 유도)
    # 먼저 현재 default branch 이름 확인
    r = subprocess.run(
        ["git", "rev-parse", "--abbrev-ref", "HEAD"],
        cwd=str(repo),
        capture_output=True,
        text=True,
    )
    default_branch = r.stdout.strip()

    # feature-conflict: conflict.txt를 다르게 수정
    subprocess.run(
        ["git", "checkout", "-b", "feature-conflict"],
        cwd=str(repo),
        capture_output=True,
        check=True,
    )
    (repo / "conflict.txt").write_text("feature branch content\n", encoding="utf-8")
    subprocess.run(["git", "add", "."], cwd=str(repo), capture_output=True, check=True)
    subprocess.run(
        ["git", "commit", "-m", "feature-conflict-change"],
        cwd=str(repo),
        capture_output=True,
        check=True,
    )

    # default branch로 돌아가서 같은 파일 수정
    subprocess.run(
        ["git", "checkout", default_branch],
        cwd=str(repo),
        capture_output=True,
        check=True,
    )
    (repo / "conflict.txt").write_text("main branch content\n", encoding="utf-8")
    subprocess.run(["git", "add", "."], cwd=str(repo), capture_output=True, check=True)
    subprocess.run(
        ["git", "commit", "-m", "main-change"],
        cwd=str(repo),
        capture_output=True,
        check=True,
    )

    preflight = PreFlightCheck(workspace_path=str(tmp_path))
    result = preflight.simulate_merge(str(repo), ["feature-conflict"])

    assert result["passed"] is False
    assert len(result["conflicts"]) >= 1
    conflict_branches = [c["branch"] for c in result["conflicts"]]
    assert "feature-conflict" in conflict_branches


# ---------------------------------------------------------------------------
# L3 - IntegrationTestRunner 테스트
# ---------------------------------------------------------------------------


def test_integration_test_runner_pass(tmp_path):
    """pytest가 성공하면 passed=True를 반환해야 한다."""
    # pass 테스트 파일 생성
    test_dir = tmp_path / "tests" / "integration"
    test_dir.mkdir(parents=True, exist_ok=True)
    (test_dir / "test_simple_pass.py").write_text(
        "def test_always_pass():\n    assert True\n",
        encoding="utf-8",
    )

    runner = IntegrationTestRunner(workspace_path=str(tmp_path))
    result = runner.run_tests(test_dir=str(test_dir))

    assert result["passed"] is True
    assert "passed" in result["output"] or result["test_count"] >= 1


def test_integration_test_runner_fail(tmp_path):
    """pytest가 실패하면 passed=False를 반환해야 한다."""
    test_dir = tmp_path / "tests" / "integration"
    test_dir.mkdir(parents=True, exist_ok=True)
    (test_dir / "test_simple_fail.py").write_text(
        "def test_always_fail():\n    assert False, 'intentional failure'\n",
        encoding="utf-8",
    )

    runner = IntegrationTestRunner(workspace_path=str(tmp_path))
    result = runner.run_tests(test_dir=str(test_dir))

    assert result["passed"] is False


def test_integration_test_runner_generate_report(tmp_path):
    """테스트 결과 리포트가 memory/reports/에 생성되어야 한다."""
    test_dir = tmp_path / "tests" / "integration"
    test_dir.mkdir(parents=True, exist_ok=True)
    (test_dir / "test_report_gen.py").write_text(
        "def test_for_report():\n    assert True\n",
        encoding="utf-8",
    )

    runner = IntegrationTestRunner(workspace_path=str(tmp_path))
    test_result = runner.run_tests(test_dir=str(test_dir))
    report_path = runner.generate_report("batch-TEST", test_result)

    # 파일 존재 확인
    assert report_path.exists()

    # 내용 검증
    content = report_path.read_text(encoding="utf-8")
    assert "batch-TEST" in content
    assert "# Integration Test Report" in content
    assert "status" in content


# ---------------------------------------------------------------------------
# 전체 파이프라인 테스트
# ---------------------------------------------------------------------------


def test_graduated_auto_gate_full_pipeline(tmp_path):
    """3팀 완료 → L1 감지 → L2 충돌 없음 → L3 PASS 검증.
    이것이 task 지시서의 핵심 검증 시나리오."""

    # ── setup: events + task-timers.json ──
    events = _make_events_dir(tmp_path)

    teams = ["dev1", "dev2", "dev3"]
    for i, team in enumerate(teams, 1):
        (events / f"task-{i}.done").write_text(
            json.dumps(
                {
                    "task_id": f"task-{i}",
                    "batch_id": "batch-pipe",
                    "status": "done",
                    "team_id": team,
                }
            ),
            encoding="utf-8",
        )

    _make_timers(
        tmp_path,
        {
            "task-1": {"batch_id": "batch-pipe", "status": "done"},
            "task-2": {"batch_id": "batch-pipe", "status": "done"},
            "task-3": {"batch_id": "batch-pipe", "status": "done"},
        },
    )

    # ── L3용 패스 테스트 파일 생성 ──
    int_test_dir = tmp_path / "tests" / "integration"
    int_test_dir.mkdir(parents=True, exist_ok=True)
    (int_test_dir / "test_pipeline_pass.py").write_text(
        "def test_pipeline_ok():\n    assert True\n",
        encoding="utf-8",
    )

    gate = GraduatedAutoGate(workspace_path=str(tmp_path))

    # Telegram 전송 관련 메서드 mock
    with (
        patch.object(gate.watchdog, "send_ttl_warning"),
        patch.object(gate.preflight, "send_conflict_alert"),
        patch.object(gate.test_runner, "send_result"),
        # L2 simulate_merge: 충돌 없음 반환 (실제 git repo 없음)
        patch.object(
            gate.preflight,
            "find_merge_branches",
            return_value=[],  # branch 없으면 L2 passed=True
        ),
        # L3 run_tests: 통합 테스트 디렉터리를 tmp_path 기준으로 실행
        patch.object(
            gate.test_runner,
            "run_tests",
            return_value={
                "passed": True,
                "output": "1 passed",
                "duration": 0.1,
                "test_count": 1,
            },
        ),
    ):
        result = gate.run()

    assert result["batches_checked"] >= 1
    assert result["passed"] >= 1
