"""
하누만(Hanuman), dev4팀 테스터
동적 멤버 매핑 검증 테스트 스위트

이 테스트 스위트는 parse-member-id.sh가 organization-structure.json에서
동적으로 매핑을 로드하고 정확하게 에이전트 ID를 추출하는지 검증합니다.
"""

import json
import subprocess
from pathlib import Path

import pytest

# ═════════════════════════════════════════════════════════════════════
# 설정 및 경로 정의
# ═════════════════════════════════════════════════════════════════════

ORG_STRUCTURE_PATH = Path("/home/jay/workspace/memory/organization-structure.json")
PARSE_MEMBER_ID_PATH = Path("/home/jay/.claude/hooks/lib/parse-member-id.sh")
GENERATOR_PATH = Path("/home/jay/.claude/hooks/lib/generate-member-map.py")
CACHE_PATH = Path("/home/jay/.claude/hooks/lib/.member-map-cache.sh")


# ═════════════════════════════════════════════════════════════════════
# 유틸리티 함수
# ═════════════════════════════════════════════════════════════════════


def _call_parse_member_id(text: str) -> str:
    """
    bash에서 parse_member_id 함수를 호출하고 결과 반환.

    Args:
        text: 에이전트 이름을 포함한 텍스트

    Returns:
        추출된 에이전트 ID (없으면 빈 문자열)
    """
    if not PARSE_MEMBER_ID_PATH.exists():
        pytest.skip(f"parse-member-id.sh not found: {PARSE_MEMBER_ID_PATH}")

    result = subprocess.run(
        [
            "bash",
            "-c",
            f'source "{PARSE_MEMBER_ID_PATH}" && parse_member_id "$1"',
            "_",
            text,
        ],
        capture_output=True,
        text=True,
        timeout=10,
    )
    return result.stdout.strip()


def _extract_ids_from_org() -> set[str]:
    """
    organization-structure.json에서 유효한 에이전트 ID 추출.

    consulting-lead와 venus-delegate는 제외합니다.

    Returns:
        추출된 ID들의 집합
    """
    if not ORG_STRUCTURE_PATH.exists():
        pytest.skip(f"Organization structure not found: {ORG_STRUCTURE_PATH}")

    with ORG_STRUCTURE_PATH.open() as f:
        org = json.load(f)

    ids = set()
    _collect_ids(org.get("structure", {}), ids)
    return ids


def _collect_ids(node, collected: set[str]):
    """
    재귀적으로 JSON 구조에서 id 필드를 수집합니다.

    consulting-lead와 venus-delegate는 필터링됩니다.

    Args:
        node: JSON 노드 (dict, list, 또는 기타)
        collected: ID를 수집할 set
    """
    if isinstance(node, dict):
        raw_id = node.get("id")
        if raw_id and isinstance(raw_id, str):
            # 제외 대상 필터링
            if raw_id not in ("consulting-lead", "venus-delegate"):
                collected.add(raw_id)
        for v in node.values():
            _collect_ids(v, collected)
    elif isinstance(node, list):
        for item in node:
            _collect_ids(item, collected)


# ═════════════════════════════════════════════════════════════════════
# 테스트 함수
# ═════════════════════════════════════════════════════════════════════


class TestDynamicMemberMapping:
    """동적 멤버 매핑 테스트 클래스"""

    def test_cache_generation(self):
        """
        generate-member-map.py 실행 시 캐시 파일이 생성되는지 검증.

        캐시 파일이 존재하고 크기가 0보다 커야 합니다.
        """
        if not GENERATOR_PATH.exists():
            pytest.skip(f"generate-member-map.py not found: {GENERATOR_PATH}")

        # 캐시 파일이 존재하는 경우 기존 파일 정보 확인
        if CACHE_PATH.exists():
            assert (
                CACHE_PATH.stat().st_size > 0
            ), f"Cache file exists but is empty: {CACHE_PATH}"
        else:
            # 생성되지 않은 경우, 생성 시도
            result = subprocess.run(
                ["python3", str(GENERATOR_PATH)],
                capture_output=True,
                text=True,
                timeout=30,
            )

            # 생성 후 존재 여부와 크기 확인
            assert (
                CACHE_PATH.exists()
            ), f"Cache file was not created: {CACHE_PATH}\n{result.stderr}"
            assert CACHE_PATH.stat().st_size > 0, f"Cache file is empty: {CACHE_PATH}"

    def test_all_org_members_mapped_dynamically(self):
        """
        organization-structure.json의 모든 에이전트가 parse-member-id에서 매핑 가능한지 검증.

        - org-structure.json에서 재귀적으로 모든 id 추출 (consulting-lead, venus-delegate 제외)
        - parse-member-id.sh가 major names를 포함하고 있는지 확인 (spot check)
        - 누락된 주요 멤버가 없어야 함
        """
        org_ids = _extract_ids_from_org()

        if not org_ids:
            pytest.skip("No member IDs found in organization structure")

        # 메이저 에이전트들이 적어도 영어 이름으로는 매핑되는지 확인
        major_agents = [
            ("kartikeya", "Kartikeya"),  # Dev4팀
            ("vishnu", "Vishnu"),  # Dev4팀장
            ("hanuman", "Hanuman"),  # Dev4 테스터
            ("hermes", "Hermes"),  # Dev1팀장
            ("odin", "Odin"),  # Dev2팀장
            ("loki", "Loki"),  # 보안팀장
            ("aphrodite", "Aphrodite"),  # 마케팅팀장
        ]

        unmapped = []
        for member_id, name in major_agents:
            result = _call_parse_member_id(name)
            if result != member_id:
                unmapped.append(f"{member_id} (from '{name}')")

        if unmapped:
            pytest.fail(f"Following major agents are not properly mapped: {unmapped}")

    def test_parse_basic_names(self):
        """
        bash에서 parse_member_id 함수 호출하여 기본 매핑 동작 검증.

        테스트 케이스:
        - 한국어: "카르티케야가 작업합니다" → "kartikeya"
        - 영어: "Vishnu is reviewing" → "vishnu"
        - 마케팅: "아프로디테 팀장님" → "aphrodite"
        - dev8: "아누비스 코드 리뷰" → "anubis" (dev8 팀장 ra)
        """
        test_cases = [
            ("카르티케야가 작업합니다", "kartikeya"),
            ("Vishnu is reviewing", "vishnu"),
            ("아프로디테 팀장님", "aphrodite"),
            ("아누비스 코드 리뷰", "anubis"),
        ]

        for text, expected_id in test_cases:
            result = _call_parse_member_id(text)
            assert (
                result == expected_id
            ), f"Failed for '{text}': expected '{expected_id}', got '{result}'"

    def test_substring_collision_prevention(self):
        """
        substring 충돌 방지 검증.

        테스트 케이스:
        - "아누비스가 코드 리뷰" → "anubis" (not "anu")
        - "아누 실장님" → "anu"
        - "라타토스크 보고" → "ratatoskr" (not "라" 매칭 아님)
        """
        test_cases = [
            ("아누비스가 코드 리뷰", "anubis"),
            ("아누 실장님", "anu"),
            ("라타토스크 보고", "ratatoskr"),
        ]

        for text, expected_id in test_cases:
            result = _call_parse_member_id(text)
            assert (
                result == expected_id
            ), f"Failed for '{text}': expected '{expected_id}', got '{result}'"

    def test_short_name_pcre_boundaries(self):
        """
        짧은 이름의 PCRE 경계 검증.

        테스트 케이스:
        - "루 백엔드 작업" → "lugh" (독립 단어로 매칭)
        - "루비처럼" → "" (빈 문자열 — 다른 단어의 일부이므로 false positive 아님)
        - "에코 데이터 수집" → "echo"
        - "에코시스템" → "" (빈 문자열)
        """
        test_cases = [
            ("루 백엔드 작업", "lugh"),
            ("루비처럼", ""),
            ("에코 데이터 수집", "echo"),
            ("에코시스템", ""),
        ]

        for text, expected_id in test_cases:
            result = _call_parse_member_id(text)
            assert (
                result == expected_id
            ), f"Failed for '{text}': expected '{expected_id}', got '{result}'"

    def test_special_english_names(self):
        """
        특수 영어 이름 매핑 검증.

        테스트 케이스:
        - "Ah Kin designed" → "ah_kin"
        - "Ma'at 검증" → "maat"
        - "Da Vinci 아이디어" → "davinci"
        """
        test_cases = [
            ("Ah Kin designed", "ah_kin"),
            ("Ma'at 검증", "maat"),
            ("Da Vinci 아이디어", "davinci"),
        ]

        for text, expected_id in test_cases:
            result = _call_parse_member_id(text)
            assert (
                result == expected_id
            ), f"Failed for '{text}': expected '{expected_id}', got '{result}'"

    def test_no_false_positive(self):
        """
        무관 텍스트에서 false positive 없음을 검증.

        테스트 케이스:
        - "오늘 날씨가 맑습니다" → ""
        - "서버 배포 완료" → ""
        - "random English text" → ""
        """
        test_cases = [
            ("오늘 날씨가 맑습니다", ""),
            ("서버 배포 완료", ""),
            ("random English text", ""),
        ]

        for text, expected_id in test_cases:
            result = _call_parse_member_id(text)
            assert (
                result == expected_id
            ), f"Failed for '{text}': expected empty string, got '{result}'"

    def test_cache_staleness_detection(self):
        """
        캐시 갱신 체크.

        캐시 파일의 mtime을 과거로 설정하고 parse-member-id.sh 소싱 시
        캐시가 적절히 관리되는지 확인합니다.
        """
        if not CACHE_PATH.exists():
            pytest.skip("Cache file not found, skipping staleness test")

        # 캐시 파일의 현재 크기 확인
        assert CACHE_PATH.stat().st_size > 0, "Cache file is empty"

        # parse-member-id.sh를 소싱해도 캐시가 있으면 재생성 안 함
        # (이는 generate-member-map.py의 역할)
        result = subprocess.run(
            ["bash", "-c", f'source "{PARSE_MEMBER_ID_PATH}" && echo "loaded"'],
            capture_output=True,
            text=True,
            timeout=10,
        )

        assert result.returncode == 0, "parse-member-id.sh sourcing failed"
        assert "loaded" in result.stdout, "parse-member-id.sh not sourced properly"

    def test_korean_name_parsing(self):
        """
        한국어 이름 파싱 검증.

        모든 한국어 이름이 정상적으로 매핑되는지 확인합니다.
        """
        korean_test_cases = [
            ("헤르메스 검토", "hermes"),
            ("불칸이 작업", "vulcan"),
            ("이리스가 디자인", "iris"),
            ("아테나를 호출", "athena"),
            ("아르고스 확인", "argos"),
            ("오딘 팀장", "odin"),
            ("토르가 개발", "thor"),
            ("프레이야입니다", "freya"),
            ("미미르 의견", "mimir"),
            ("헤임달 점검", "heimdall"),
        ]

        for text, expected_id in korean_test_cases:
            result = _call_parse_member_id(text)
            assert (
                result == expected_id
            ), f"Failed for '{text}': expected '{expected_id}', got '{result}'"

    def test_english_name_parsing(self):
        """
        영어 이름 파싱 검증.

        모든 영어 이름이 정상적으로 매핑되는지 확인합니다.
        """
        english_test_cases = [
            ("Hermes is here", "hermes"),
            ("Vulcan works", "vulcan"),
            ("Iris designed", "iris"),
            ("Athena talks", "athena"),
            ("Argos checking", "argos"),
            ("Odin leads", "odin"),
            ("Thor develops", "thor"),
            ("Freya is present", "freya"),
            ("Mimir suggests", "mimir"),
            ("Heimdall guards", "heimdall"),
        ]

        for text, expected_id in english_test_cases:
            result = _call_parse_member_id(text)
            assert (
                result == expected_id
            ), f"Failed for '{text}': expected '{expected_id}', got '{result}'"

    def test_mixed_language_parsing(self):
        """
        한국어 + 영어 혼합 텍스트 파싱 검증.

        한국어와 영어가 혼합된 텍스트에서도 정상적으로 매핑되는지 확인합니다.
        """
        mixed_test_cases = [
            ("헤르메스(Hermes) 리뷰", "hermes"),
            ("Vulcan 불칸이 작업", "vulcan"),
            ("아테나 Athena 의견", "athena"),
            ("프로메테우스 Prometheus 전략", "prometheus"),
            ("마아트 Ma'at 검증", "maat"),
        ]

        for text, expected_id in mixed_test_cases:
            result = _call_parse_member_id(text)
            assert (
                result == expected_id
            ), f"Failed for '{text}': expected '{expected_id}', got '{result}'"


# ═════════════════════════════════════════════════════════════════════
# 조건부 테스트 클래스
# ═════════════════════════════════════════════════════════════════════


class TestCacheIntegration:
    """캐시 통합 테스트 (선택)"""

    def test_cache_contains_bash_functions(self):
        """
        캐시 파일이 bash 함수를 포함하는지 검증.

        캐시 파일에 `echo "xxx"` 패턴의 매핑이 포함되어 있는지 확인합니다.
        """
        if not CACHE_PATH.exists():
            pytest.skip("Cache file not found")

        with CACHE_PATH.open() as f:
            cache_content = f.read()

        # 캐시가 bash echo 문을 포함하는지 확인
        assert "echo" in cache_content, "Cache file does not contain echo statements"


# ═════════════════════════════════════════════════════════════════════
# 메인 (pytest 실행 시 자동 로드)
# ═════════════════════════════════════════════════════════════════════

if __name__ == "__main__":
    pytest.main([__file__, "-v"])
