"""tests/regression/test_composite_v3_cross_cutting_2530.py

회귀 테스트 — task-2530 composite ③ 카테고리 dispatch 코드 정식 구현.

회장 §본질 (2026-05-10):
  composite 작업 3종 분류 중 ③ 카테고리(개발팀 물리봇 + 여러 팀 + 횡단조직 agent
  복합)가 미구현 상태였음. dispatch 코드를 정식 구현하여 cross-cutting agent를
  composite member로 합법적으로 받아들이고 페르소나를 합성한다.

회장 §명시 5 회귀 (정확히 5개):
  1. ① logical 1팀 회귀 — 기존 `--team marketing` 단일 경로 유지 (composite 미진입)
  2. ② logical 2~3팀 복합 회귀 — `--composite marketing,design` ②_logical_only 분류
  3. ③ logical + cross-cutting mix success — `--composite marketing,loki` → ③_mixed + 페르소나 합성
  4. ③ 잘못된 cross-cutting ID rejection — `--composite marketing,nonexistent` → ValueError
  5. ③ 페르소나 합성 검증 + trigger gate — prompt OFF/agent 라벨 + bot_status COMPOSITE_TRIGGER_KEYWORDS 동작
"""
from __future__ import annotations

import sys
from pathlib import Path

import pytest

# ---------------------------------------------------------------------------
# Worktree root → sys.path (force position 0) — 패턴 task-2486/task-2523/task-2526 정합
# ---------------------------------------------------------------------------
_WORKTREE_ROOT = Path(__file__).resolve().parent.parent.parent
if str(_WORKTREE_ROOT) in sys.path:
    sys.path.remove(str(_WORKTREE_ROOT))
sys.path.insert(0, str(_WORKTREE_ROOT))

from utils.composite_constants import (  # noqa: E402  # pyright: ignore[reportMissingImports]
    COMPOSITE_ALL_ALLOWED,
    COMPOSITE_ALLOWED_TEAMS,
    COMPOSITE_CROSS_CUTTING_AGENTS,
    COMPOSITE_TRIGGER_KEYWORDS,
    CROSS_CUTTING_AGENT_LABELS,
    HANDOFF_REQUIRED_FIELDS,
    MAX_COMPOSITE_TEAMS,
)
from dispatch import (  # noqa: E402  # pyright: ignore[reportMissingImports]
    _build_composite_iii_prompt,
    _build_persona_prompt,
    _classify_composite_pattern,
    _validate_composite_members,
    _validate_composite_teams,
)


# ---------------------------------------------------------------------------
# Test 1: ① logical 1팀 회귀 — 기존 `--team` 단일 경로 유지 (composite 미진입)
# ---------------------------------------------------------------------------
def test_legacy_single_team_unchanged():
    """① logical 1팀: --team marketing 단일 위임 경로는 변경 없음 + composite 4팀 박제 유지."""
    # composite validator는 1개 멤버를 거부 (단일팀은 --team 전용 경로)
    with pytest.raises(ValueError, match="2개 이상"):
        _validate_composite_members("marketing")

    # 기존 COMPOSITE_ALLOWED_TEAMS 4개 박제 (확장만, 제거 X)
    assert "marketing" in COMPOSITE_ALLOWED_TEAMS
    assert "design" in COMPOSITE_ALLOWED_TEAMS
    assert "consulting" in COMPOSITE_ALLOWED_TEAMS
    assert "publishing" in COMPOSITE_ALLOWED_TEAMS
    assert len(COMPOSITE_ALLOWED_TEAMS) == 4

    # MAX_COMPOSITE_TEAMS = 3 박제 (변경 금지)
    assert MAX_COMPOSITE_TEAMS == 3


# ---------------------------------------------------------------------------
# Test 2: ② logical 2~3팀 복합 회귀 — 기존 동작 PASS
# ---------------------------------------------------------------------------
def test_logical_only_composite_pattern_unchanged():
    """② logical 2~3팀 복합: marketing,design은 ②_logical_only로 분류되고 기존 동작 유지."""
    # 2팀
    members2 = _validate_composite_members("marketing,design")
    assert members2 == ["marketing", "design"]
    assert _classify_composite_pattern(members2) == "②_logical_only"

    # 3팀
    members3 = _validate_composite_members("marketing,design,consulting")
    assert members3 == ["marketing", "design", "consulting"]
    assert _classify_composite_pattern(members3) == "②_logical_only"

    # MAX 초과 거부
    with pytest.raises(ValueError, match="최대"):
        _validate_composite_members("marketing,design,consulting,publishing")

    # backward-compat alias도 동일 동작
    assert _validate_composite_teams("marketing,design") == ["marketing", "design"]


# ---------------------------------------------------------------------------
# Test 3: ③ logical + cross-cutting mix success — 페르소나 합성 PASS
# ---------------------------------------------------------------------------
def test_mixed_logical_cross_cutting_success():
    """③ logical + cross-cutting mix: marketing,loki → ③_mixed 분류 + 7개 cross-cutting 통과."""
    # 회장 §결정 7 cross-cutting agent 박제
    expected_agents = {"loki", "maat", "janus", "venus", "atlas", "prometheus", "chronos"}
    assert COMPOSITE_CROSS_CUTTING_AGENTS == frozenset(expected_agents)

    # marketing + loki (③_mixed)
    members = _validate_composite_members("marketing,loki")
    assert members == ["marketing", "loki"]
    assert _classify_composite_pattern(members) == "③_mixed"

    # cross-cutting only 조합 (③_cross_cutting_only)
    cc_only = _validate_composite_members("maat,loki")
    assert _classify_composite_pattern(cc_only) == "③_cross_cutting_only"

    # 7개 agent 각각 marketing과 mix 시 모두 통과
    for agent in expected_agents:
        members = _validate_composite_members(f"marketing,{agent}")
        assert agent in members
        assert "marketing" in members
        assert _classify_composite_pattern(members) == "③_mixed"

    # COMPOSITE_ALL_ALLOWED는 logical 4 + cross-cutting 7 = 11
    assert len(COMPOSITE_ALL_ALLOWED) == 11


# ---------------------------------------------------------------------------
# Test 4: ③ 잘못된 cross-cutting ID rejection — validation error
# ---------------------------------------------------------------------------
def test_invalid_member_rejection():
    """③ 잘못된 cross-cutting ID: marketing,nonexistent → ValueError + alias 동일 거부."""
    with pytest.raises(ValueError, match="알 수 없는 composite 멤버"):
        _validate_composite_members("marketing,nonexistent")

    # 알려진 dev 봇 ID도 거부 (composite member 허용 set 밖)
    with pytest.raises(ValueError, match="알 수 없는 composite 멤버"):
        _validate_composite_members("marketing,dev1-team")

    # 중복 멤버 거부
    with pytest.raises(ValueError, match="중복 멤버"):
        _validate_composite_members("marketing,marketing")

    # backward-compat alias도 동일 거부
    with pytest.raises(ValueError, match="알 수 없는 composite 멤버"):
        _validate_composite_teams("marketing,nonexistent")

    # 빈 입력 거부
    with pytest.raises(ValueError, match="2개 이상"):
        _validate_composite_members("")


# ---------------------------------------------------------------------------
# Test 5: ③ 페르소나 합성 검증 + bot_status COMPOSITE_TRIGGER_KEYWORDS gate
# ---------------------------------------------------------------------------
def test_persona_synthesis_and_trigger_gate(tmp_path, monkeypatch):
    """③ 페르소나 합성: prompt에 'dev 본인 페르소나 OFF' + 로키/마아트 라벨 포함.

    추가로 utils/bot_status.py:367 보강이 동작하는지 검증:
      - trigger 키워드 미포함 → composite 후보 진입 X (기존 동작)
      - trigger 키워드 포함 + composite 키워드 매칭 → 후보 진입 O
    """
    # ── 페르소나 합성 prompt ──────────────────────────────────────
    persona_mixed = _build_persona_prompt(["marketing"], ["loki", "maat"])
    assert "dev 본인 페르소나 OFF" in persona_mixed
    assert CROSS_CUTTING_AGENT_LABELS["loki"] in persona_mixed  # "로키(보안 redteam)"
    assert CROSS_CUTTING_AGENT_LABELS["maat"] in persona_mixed
    assert "marketing" in persona_mixed

    # cross-cutting only도 OFF + agent 라벨 포함
    persona_cc_only = _build_persona_prompt([], ["maat", "loki"])
    assert "dev 본인 페르소나 OFF" in persona_cc_only
    assert "로키" in persona_cc_only and "마아트" in persona_cc_only

    # ── _build_composite_iii_prompt 통합 출력 ────────────────────
    full_prompt = _build_composite_iii_prompt(
        logical_members=["marketing"],
        cross_cutting_members=["loki"],
        task_id="task-2530.persona-test",
        task_desc="redteam 검증 task",
        level="normal",
    )
    assert "dev 본인 페르소나 OFF" in full_prompt
    assert CROSS_CUTTING_AGENT_LABELS["loki"] in full_prompt
    assert "task-2530.persona-test" in full_prompt
    # ③ 카테고리 핸드오프 필드 포함 (marketing+loki 조합 fixture)
    assert "redteam 공격 시나리오" in full_prompt

    # critical / security 헤더 분기
    critical_prompt = _build_composite_iii_prompt(
        logical_members=["consulting"],
        cross_cutting_members=["loki"],
        task_id="task-2530.crit",
        task_desc="t",
        level="critical",
    )
    assert "[CRITICAL]" in critical_prompt
    security_prompt = _build_composite_iii_prompt(
        logical_members=["design"],
        cross_cutting_members=["maat"],
        task_id="task-2530.sec",
        task_desc="t",
        level="security",
    )
    assert "[SECURITY]" in security_prompt

    # HANDOFF_REQUIRED_FIELDS ③ 조합 박제
    assert frozenset({"marketing", "loki"}) in HANDOFF_REQUIRED_FIELDS
    assert frozenset({"design", "maat"}) in HANDOFF_REQUIRED_FIELDS
    assert frozenset({"consulting", "loki"}) in HANDOFF_REQUIRED_FIELDS
    assert frozenset({"publishing", "maat"}) in HANDOFF_REQUIRED_FIELDS

    # ── bot_status COMPOSITE_TRIGGER_KEYWORDS gate 박제 ──────────
    # 회장 §결정 trigger 6개 박제
    expected_triggers = {"검증", "감사", "QC", "redteam", "보안검토", "복합검증"}
    assert COMPOSITE_TRIGGER_KEYWORDS == frozenset(expected_triggers)

    # BotStatusManager 직접 동작 확인 — trigger 키워드 유무에 따라 composite 후보 변화
    fake_workspace = tmp_path
    monkeypatch.setenv("WORKSPACE_ROOT", str(fake_workspace))

    from utils.bot_status import BotStatusManager  # noqa: E402  # pyright: ignore[reportMissingImports]

    mgr = BotStatusManager(workspace_root=fake_workspace)
    # logical_teams 가짜 데이터 주입 — composite은 trigger 키워드 매칭으로만 후보 진입
    fake_logical_teams = {
        "design": {"keywords": ["디자인"], "anti_keywords": []},
        "composite": {"keywords": ["복합검증"], "anti_keywords": []},
    }
    monkeypatch.setattr(mgr, "_load_logical_teams", lambda: fake_logical_teams)

    # trigger 키워드 미포함: composite 후보 진입 X (design만 후보)
    assert mgr.suggest_team("디자인 작업") == "design"
    assert mgr.suggest_team("일반 작업") is None

    # trigger 키워드 + composite keyword 매칭: composite 후보 진입 O
    assert mgr.suggest_team("복합검증 작업") == "composite"
