#!/usr/bin/env python3
"""
아르고스 (테스터) — event-queue.py 전체 테스트
대상: /home/jay/workspace/memory/event-queue.py
"""

import json
import multiprocessing
import os
import subprocess
import sys
import tempfile
import time
from pathlib import Path

import importlib.util

import pytest

# event-queue.py 위치
EVENT_QUEUE_PY = "/home/jay/workspace/memory/event-queue.py"
# team_prompts.py 위치 (import를 위해 sys.path 추가)
PROMPTS_DIR = "/home/jay/workspace/prompts"


def _import_event_queue():
    """event-queue.py를 importlib로 로드 (하이픈 포함 파일명 대응)."""
    spec = importlib.util.spec_from_file_location("event_queue", EVENT_QUEUE_PY)
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module


# ─────────────────────────────────────────────────────────────────────────────
# 공통 fixture
# ─────────────────────────────────────────────────────────────────────────────

@pytest.fixture()
def tmp_queue_file(tmp_path):
    """테스트마다 독립적인 임시 큐 파일 경로를 제공한다."""
    qf = str(tmp_path / "test-event-queue.json")
    return qf


@pytest.fixture()
def eq(tmp_queue_file):
    """EventQueue 인스턴스 (임시 큐 파일 사용)."""
    module = _import_event_queue()
    return module.EventQueue(queue_file=tmp_queue_file)


# ─────────────────────────────────────────────────────────────────────────────
# 테스트 1: FIFO 순서 보장
# ─────────────────────────────────────────────────────────────────────────────

class TestFIFOOrder:
    """enqueue 3건 후 FIFO 순서대로 peek/dequeue 확인"""

    def test_fifo_enqueue_and_peek(self, eq):
        """enqueue 3건 후 peek은 가장 먼저 넣은 이벤트를 반환해야 한다."""
        eq.enqueue("task_complete", "task-A", "dev1-team", "/path/a.md")
        eq.enqueue("task_complete", "task-B", "dev2-team", "/path/b.md")
        eq.enqueue("task_complete", "task-C", "dev3-team", "/path/c.md")

        first = eq.peek()
        assert first is not None, "peek()은 None을 반환하면 안 됩니다"
        assert first["task_id"] == "task-A", f"첫 peek은 task-A이어야 하지만 {first['task_id']} 반환"

    def test_fifo_dequeue_order(self, eq):
        """dequeue 순서가 enqueue 순서와 동일해야 한다 (FIFO)."""
        eq.enqueue("task_complete", "task-A", "dev1-team", "/path/a.md")
        eq.enqueue("task_complete", "task-B", "dev2-team", "/path/b.md")
        eq.enqueue("task_complete", "task-C", "dev3-team", "/path/c.md")

        # 첫 번째: task-A
        first = eq.peek()
        assert first["task_id"] == "task-A"
        removed_a = eq.dequeue(first["id"])
        assert removed_a is not None
        assert removed_a["task_id"] == "task-A"

        # 두 번째: task-B
        second = eq.peek()
        assert second is not None, "task-A dequeue 후 peek은 task-B이어야 합니다"
        assert second["task_id"] == "task-B", f"두 번째 peek은 task-B이어야 하지만 {second['task_id']} 반환"
        removed_b = eq.dequeue(second["id"])
        assert removed_b["task_id"] == "task-B"

        # 세 번째: task-C
        third = eq.peek()
        assert third is not None, "task-B dequeue 후 peek은 task-C이어야 합니다"
        assert third["task_id"] == "task-C", f"세 번째 peek은 task-C이어야 하지만 {third['task_id']} 반환"

    def test_fifo_count_decreases_after_dequeue(self, eq):
        """dequeue 후 count가 감소해야 한다."""
        eq.enqueue("task_complete", "task-A", "dev1-team", "/path/a.md")
        eq.enqueue("task_complete", "task-B", "dev2-team", "/path/b.md")
        eq.enqueue("task_complete", "task-C", "dev3-team", "/path/c.md")

        assert eq.count() == 3

        first = eq.peek()
        eq.dequeue(first["id"])
        assert eq.count() == 2

        second = eq.peek()
        eq.dequeue(second["id"])
        assert eq.count() == 1

    def test_fifo_processed_list(self, eq):
        """dequeue된 이벤트는 processed 목록에 존재해야 한다."""
        eq.enqueue("task_complete", "task-A", "dev1-team", "/path/a.md")
        first = eq.peek()
        eq.dequeue(first["id"])

        result = eq.list_events(include_processed=True)
        processed_ids = [e["task_id"] for e in result["processed"]]
        assert "task-A" in processed_ids, "dequeue된 task-A가 processed에 없습니다"


# ─────────────────────────────────────────────────────────────────────────────
# 테스트 2: 동시 enqueue (멀티프로세스)
# ─────────────────────────────────────────────────────────────────────────────

def _worker_enqueue(args):
    """멀티프로세스 워커: 각 프로세스가 이벤트 하나씩 enqueue."""
    queue_file, idx = args
    import importlib.util as _ilu
    spec = _ilu.spec_from_file_location("event_queue", "/home/jay/workspace/memory/event-queue.py")
    module = _ilu.module_from_spec(spec)
    spec.loader.exec_module(module)
    eq = module.EventQueue(queue_file=queue_file)
    try:
        evt = eq.enqueue("task_complete", f"task-P{idx}", f"dev{idx}-team", f"/path/p{idx}.md")
        return evt["id"]
    except Exception as e:
        return f"ERROR:{e}"


class TestConcurrentEnqueue:
    """멀티프로세스 동시 enqueue — 파일 잠금으로 충돌 없음 확인"""

    def test_concurrent_enqueue_count(self, tmp_queue_file):
        """5개 프로세스가 동시에 enqueue 후 count == 5이어야 한다."""
        num_procs = 5
        with multiprocessing.Pool(processes=num_procs) as pool:
            results = pool.map(_worker_enqueue, [(tmp_queue_file, i) for i in range(num_procs)])

        # 에러 없이 완료 확인
        errors = [r for r in results if isinstance(r, str) and r.startswith("ERROR:")]
        assert not errors, f"enqueue 중 에러 발생: {errors}"

        # count 확인
        module = _import_event_queue()
        eq = module.EventQueue(queue_file=tmp_queue_file)
        count = eq.count()
        assert count == num_procs, f"count가 {num_procs}이어야 하지만 {count} 반환"

    def test_concurrent_enqueue_unique_ids(self, tmp_queue_file):
        """동시 enqueue된 이벤트 ID가 모두 유니크해야 한다."""
        num_procs = 5
        with multiprocessing.Pool(processes=num_procs) as pool:
            results = pool.map(_worker_enqueue, [(tmp_queue_file, i) for i in range(num_procs)])

        # 에러 없이 완료 확인
        ids = [r for r in results if not (isinstance(r, str) and r.startswith("ERROR:"))]
        assert len(ids) == num_procs, f"성공한 enqueue가 {num_procs}개여야 하지만 {len(ids)}개"
        assert len(ids) == len(set(ids)), f"중복 ID 발생: {ids}"


# ─────────────────────────────────────────────────────────────────────────────
# 테스트 3: 빈 큐 graceful 처리
# ─────────────────────────────────────────────────────────────────────────────

class TestEmptyQueue:
    """빈 큐에서 peek/dequeue 호출 시 에러 없이 None 반환 확인"""

    def test_peek_empty_queue(self, eq):
        """빈 큐에서 peek()은 None을 반환해야 한다."""
        result = eq.peek()
        assert result is None, f"빈 큐 peek은 None이어야 하지만 {result} 반환"

    def test_dequeue_nonexistent_event(self, eq):
        """존재하지 않는 ID로 dequeue 시 None을 반환해야 한다."""
        result = eq.dequeue("nonexistent")
        assert result is None, f"존재하지 않는 ID dequeue는 None이어야 하지만 {result} 반환"

    def test_count_empty_queue(self, eq):
        """빈 큐에서 count()는 0을 반환해야 한다."""
        assert eq.count() == 0

    def test_list_empty_queue(self, eq):
        """빈 큐에서 list_events()는 빈 queue를 반환해야 한다."""
        result = eq.list_events()
        assert result["queue"] == []

    def test_list_all_empty_queue(self, eq):
        """빈 큐에서 list_events(include_processed=True)는 빈 processed도 반환해야 한다."""
        result = eq.list_events(include_processed=True)
        assert result["queue"] == []
        assert result["processed"] == []


# ─────────────────────────────────────────────────────────────────────────────
# 테스트 4: CLI 테스트
# ─────────────────────────────────────────────────────────────────────────────

class TestCLI:
    """subprocess를 이용한 CLI 명령 테스트"""

    def _run(self, args, queue_file):
        """CLI 명령 실행 헬퍼."""
        cmd = [sys.executable, EVENT_QUEUE_PY] + args + ["--queue-file", queue_file]
        result = subprocess.run(cmd, capture_output=True, text=True)
        return result

    def test_cli_enqueue_returns_json(self, tmp_queue_file):
        """enqueue CLI 명령이 JSON을 반환해야 한다."""
        result = self._run(
            ["enqueue", "--type", "task_complete", "--task-id", "task-99",
             "--team", "dev1-team", "--report", "/tmp/test.md"],
            tmp_queue_file,
        )
        assert result.returncode == 0, f"enqueue 실패: {result.stderr}"
        data = json.loads(result.stdout)
        assert data["task_id"] == "task-99"
        assert data["type"] == "task_complete"
        assert data["status"] == "pending"
        assert "id" in data

    def test_cli_peek_returns_json(self, tmp_queue_file):
        """peek CLI 명령이 JSON을 반환해야 한다."""
        # 먼저 enqueue
        self._run(
            ["enqueue", "--type", "task_complete", "--task-id", "task-99",
             "--team", "dev1-team", "--report", "/tmp/test.md"],
            tmp_queue_file,
        )
        result = self._run(["peek"], tmp_queue_file)
        assert result.returncode == 0, f"peek 실패: {result.stderr}"
        data = json.loads(result.stdout)
        assert data["task_id"] == "task-99"

    def test_cli_peek_empty_returns_json(self, tmp_queue_file):
        """빈 큐에서 peek CLI는 {"status": "empty"}를 반환해야 한다."""
        result = self._run(["peek"], tmp_queue_file)
        assert result.returncode == 0, f"빈 큐 peek 실패: {result.stderr}"
        data = json.loads(result.stdout)
        assert data == {"status": "empty"}, f"예상: {{\"status\": \"empty\"}}, 실제: {data}"

    def test_cli_count_returns_number(self, tmp_queue_file):
        """count CLI 명령이 숫자를 반환해야 한다."""
        # enqueue 2건
        self._run(
            ["enqueue", "--type", "task_complete", "--task-id", "task-1",
             "--team", "dev1-team", "--report", "/tmp/t1.md"],
            tmp_queue_file,
        )
        self._run(
            ["enqueue", "--type", "task_complete", "--task-id", "task-2",
             "--team", "dev1-team", "--report", "/tmp/t2.md"],
            tmp_queue_file,
        )
        result = self._run(["count"], tmp_queue_file)
        assert result.returncode == 0, f"count 실패: {result.stderr}"
        count = int(result.stdout.strip())
        assert count == 2, f"count는 2이어야 하지만 {count} 반환"

    def test_cli_dequeue_removes_event(self, tmp_queue_file):
        """dequeue CLI 명령이 이벤트를 제거하고 처리 완료 JSON을 반환해야 한다."""
        # enqueue
        enq_result = self._run(
            ["enqueue", "--type", "task_complete", "--task-id", "task-99",
             "--team", "dev1-team", "--report", "/tmp/test.md"],
            tmp_queue_file,
        )
        event_id = json.loads(enq_result.stdout)["id"]

        # dequeue
        result = self._run(["dequeue", event_id], tmp_queue_file)
        assert result.returncode == 0, f"dequeue 실패: {result.stderr}"
        data = json.loads(result.stdout)
        assert data["id"] == event_id
        assert data["status"] == "processed"

        # count가 0이 되었는지 확인
        count_result = self._run(["count"], tmp_queue_file)
        assert int(count_result.stdout.strip()) == 0

    def test_cli_dequeue_nonexistent_returns_error(self, tmp_queue_file):
        """존재하지 않는 ID dequeue CLI는 에러 코드를 반환해야 한다."""
        result = self._run(["dequeue", "evt-999"], tmp_queue_file)
        assert result.returncode != 0, "존재하지 않는 ID dequeue는 nonzero 반환코드여야 합니다"
        data = json.loads(result.stdout)
        assert "error" in data

    def test_cli_list_pending(self, tmp_queue_file):
        """list --pending CLI 명령이 pending 이벤트 JSON을 반환해야 한다."""
        self._run(
            ["enqueue", "--type", "task_complete", "--task-id", "task-L1",
             "--team", "dev1-team", "--report", "/tmp/l1.md"],
            tmp_queue_file,
        )
        result = self._run(["list", "--pending"], tmp_queue_file)
        assert result.returncode == 0, f"list --pending 실패: {result.stderr}"
        data = json.loads(result.stdout)
        assert "queue" in data
        assert len(data["queue"]) == 1
        assert data["queue"][0]["task_id"] == "task-L1"

    def test_cli_list_all_includes_processed(self, tmp_queue_file):
        """list --all CLI 명령이 processed 이벤트도 포함해야 한다."""
        # enqueue
        enq_result = self._run(
            ["enqueue", "--type", "task_complete", "--task-id", "task-L2",
             "--team", "dev1-team", "--report", "/tmp/l2.md"],
            tmp_queue_file,
        )
        event_id = json.loads(enq_result.stdout)["id"]

        # dequeue (processed로 이동)
        self._run(["dequeue", event_id], tmp_queue_file)

        result = self._run(["list", "--all"], tmp_queue_file)
        assert result.returncode == 0, f"list --all 실패: {result.stderr}"
        data = json.loads(result.stdout)
        assert "queue" in data
        assert "processed" in data
        assert len(data["processed"]) == 1
        assert data["processed"][0]["task_id"] == "task-L2"


# ─────────────────────────────────────────────────────────────────────────────
# 테스트 5: user-prompt-submit.sh 훅 테스트
# ─────────────────────────────────────────────────────────────────────────────

HOOK_SCRIPT = "/home/jay/.claude/hooks/user-prompt-submit.sh"
# anu 봇으로 인식되는 cwd (user-prompt-submit.sh의 anu 케이스 실행)
ANU_CWD = "/home/jay/.cokacdir/workspace/autoset"


class TestUserPromptSubmitHook:
    """user-prompt-submit.sh 훅 실행 시 이벤트 큐 count 포함 출력 확인"""

    def test_hook_shows_pending_count_when_events_exist(self, tmp_queue_file):
        """큐에 이벤트가 있을 때 훅 실행 시 '미처리 이벤트 N건' 메시지를 출력해야 한다."""
        # 실제 큐 파일에 이벤트를 enqueue (훅은 DEFAULT_QUEUE_FILE을 사용하므로)
        default_queue = "/home/jay/workspace/memory/events/event-queue.json"

        # 현재 큐 상태 백업
        with open(default_queue, "r", encoding="utf-8") as f:
            original_content = f.read()

        try:
            # 이벤트 1건 enqueue
            module = _import_event_queue()
            eq_default = module.EventQueue()
            test_event = eq_default.enqueue(
                "task_complete", "task-hook-test", "dev1-team", "/tmp/hook-test.md"
            )

            # count 확인
            count_before = eq_default.count()
            assert count_before >= 1, "훅 테스트 전 count가 0입니다"

            # 훅 실행 (anu cwd로)
            hook_input = json.dumps({"cwd": ANU_CWD})
            result = subprocess.run(
                ["bash", HOOK_SCRIPT],
                input=hook_input,
                capture_output=True,
                text=True,
                timeout=15,
            )

            output = result.stdout + result.stderr

            # "미처리 이벤트 N건" 메시지 확인
            assert "미처리 이벤트" in output and "건" in output, (
                f"훅 출력에 '미처리 이벤트 N건' 메시지가 없습니다.\n출력:\n{output}"
            )

            # 실제 count가 출력에 포함되는지 확인
            assert str(count_before) in output, (
                f"훅 출력에 count ({count_before})가 포함되어 있지 않습니다.\n출력:\n{output}"
            )

        finally:
            # 원본 큐 파일 복구 (enqueue한 테스트 이벤트 제거)
            with open(default_queue, "w", encoding="utf-8") as f:
                f.write(original_content)

    def test_hook_no_pending_message_when_queue_empty(self):
        """큐가 비어있을 때 훅 실행 시 '미처리 이벤트' 메시지가 없어야 한다."""
        default_queue = "/home/jay/workspace/memory/events/event-queue.json"

        # 현재 큐 상태 백업
        with open(default_queue, "r", encoding="utf-8") as f:
            original_content = f.read()
            original_data = json.loads(original_content)

        # 큐가 비어있지 않으면 스킵
        if original_data.get("queue"):
            pytest.skip("현재 큐가 비어있지 않아 이 테스트를 건너뜁니다")

        try:
            # 큐가 비어있는 상태로 훅 실행
            hook_input = json.dumps({"cwd": ANU_CWD})
            result = subprocess.run(
                ["bash", HOOK_SCRIPT],
                input=hook_input,
                capture_output=True,
                text=True,
                timeout=15,
            )
            output = result.stdout + result.stderr

            # 빈 큐이면 "미처리 이벤트" 메시지가 없어야 함
            assert "미처리 이벤트" not in output or "0건" in output, (
                f"빈 큐인데 '미처리 이벤트' 메시지가 출력됩니다.\n출력:\n{output}"
            )
        finally:
            with open(default_queue, "w", encoding="utf-8") as f:
                f.write(original_content)

    def test_hook_non_anu_cwd_no_queue_output(self):
        """anu가 아닌 cwd로 훅 실행 시 이벤트 큐 메시지가 없어야 한다."""
        dev1_cwd = "/home/jay/workspace/teams/dev1"
        hook_input = json.dumps({"cwd": dev1_cwd})
        result = subprocess.run(
            ["bash", HOOK_SCRIPT],
            input=hook_input,
            capture_output=True,
            text=True,
            timeout=15,
        )
        output = result.stdout + result.stderr
        # dev1은 개발팀 규칙만 출력하고 이벤트 큐 메시지는 없어야 함
        assert "미처리 이벤트" not in output, (
            f"dev1 cwd인데 이벤트 큐 메시지가 출력됩니다.\n출력:\n{output}"
        )


# ─────────────────────────────────────────────────────────────────────────────
# 테스트 6: build_prompt() enqueue 포함 테스트
# ─────────────────────────────────────────────────────────────────────────────

class TestBuildPromptEnqueue:
    """team_prompts.py build_prompt()가 event-queue.py enqueue 명령을 포함하는지 확인"""

    def _get_build_prompt(self):
        """team_prompts.build_prompt를 import하여 반환."""
        import importlib
        spec = importlib.util.spec_from_file_location(
            "team_prompts",
            "/home/jay/workspace/prompts/team_prompts.py",
        )
        module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(module)
        return module.build_prompt

    def test_dev1_team_prompt_contains_enqueue(self):
        """dev1-team (direct 타입) 프롬프트에 event-queue.py enqueue 명령이 포함되어야 한다."""
        build_prompt = self._get_build_prompt()
        prompt = build_prompt("dev1-team", "task-test", "test desc")
        assert "event-queue.py enqueue" in prompt, (
            "dev1-team 프롬프트에 'event-queue.py enqueue' 문자열이 없습니다.\n"
            f"프롬프트 일부:\n{prompt[:500]}..."
        )

    def test_dev3_team_prompt_contains_enqueue(self):
        """dev3-team (direct 타입) 프롬프트에 event-queue.py enqueue 명령이 포함되어야 한다."""
        build_prompt = self._get_build_prompt()
        prompt = build_prompt("dev3-team", "task-test", "test desc")
        assert "event-queue.py enqueue" in prompt, (
            "dev3-team 프롬프트에 'event-queue.py enqueue' 문자열이 없습니다.\n"
            f"프롬프트 일부:\n{prompt[:500]}..."
        )

    def test_dev1_team_prompt_enqueue_has_correct_args(self):
        """dev1-team 프롬프트의 enqueue 명령에 --type, --task-id, --team, --report 인수가 있어야 한다."""
        build_prompt = self._get_build_prompt()
        prompt = build_prompt("dev1-team", "task-test", "test desc")
        for arg in ["--type", "--task-id", "--team", "--report"]:
            assert arg in prompt, f"dev1-team 프롬프트에 enqueue 인수 '{arg}'가 없습니다"

    def test_dev3_team_prompt_enqueue_has_correct_args(self):
        """dev3-team 프롬프트의 enqueue 명령에 --type, --task-id, --team, --report 인수가 있어야 한다."""
        build_prompt = self._get_build_prompt()
        prompt = build_prompt("dev3-team", "task-test", "test desc")
        for arg in ["--type", "--task-id", "--team", "--report"]:
            assert arg in prompt, f"dev3-team 프롬프트에 enqueue 인수 '{arg}'가 없습니다"

    def test_dev2_team_prompt_contains_enqueue(self):
        """dev2-team (direct 타입) 프롬프트에도 event-queue.py enqueue 명령이 포함되어야 한다."""
        build_prompt = self._get_build_prompt()
        prompt = build_prompt("dev2-team", "task-test", "test desc")
        assert "event-queue.py enqueue" in prompt, (
            "dev2-team 프롬프트에 'event-queue.py enqueue' 문자열이 없습니다."
        )


# ─────────────────────────────────────────────────────────────────────────────
# 추가: event_id 형식 검증
# ─────────────────────────────────────────────────────────────────────────────

class TestEventIdFormat:
    """이벤트 ID 형식 검증"""

    def test_event_id_format(self, eq):
        """enqueue된 이벤트의 ID는 evt-NNN 형식이어야 한다."""
        evt = eq.enqueue("task_complete", "task-X", "dev1-team", "/path/x.md")
        assert evt["id"].startswith("evt-"), f"ID 형식이 evt-로 시작해야 하지만: {evt['id']}"
        suffix = evt["id"][4:]
        assert suffix.isdigit(), f"ID 숫자 부분이 숫자여야 하지만: {suffix}"

    def test_sequential_ids_are_increasing(self, eq):
        """순차 enqueue 시 ID 시퀀스 번호가 증가해야 한다."""
        evt1 = eq.enqueue("task_complete", "task-1", "dev1-team", "/path/1.md")
        evt2 = eq.enqueue("task_complete", "task-2", "dev1-team", "/path/2.md")
        evt3 = eq.enqueue("task_complete", "task-3", "dev1-team", "/path/3.md")

        seq1 = int(evt1["id"][4:])
        seq2 = int(evt2["id"][4:])
        seq3 = int(evt3["id"][4:])

        assert seq1 < seq2 < seq3, f"ID 시퀀스가 증가해야 하지만: {seq1}, {seq2}, {seq3}"


# ─────────────────────────────────────────────────────────────────────────────
# 추가: list_events 필터링 검증
# ─────────────────────────────────────────────────────────────────────────────

class TestListEvents:
    """list_events() 필터링 동작 검증"""

    def test_list_pending_only(self, eq):
        """list_events()는 기본적으로 queue만 포함하고 processed는 없어야 한다."""
        eq.enqueue("task_complete", "task-A", "dev1-team", "/path/a.md")
        result = eq.list_events()
        assert "queue" in result
        assert "processed" not in result

    def test_list_all_includes_both(self, eq):
        """list_events(include_processed=True)는 queue와 processed 모두 포함해야 한다."""
        evt = eq.enqueue("task_complete", "task-A", "dev1-team", "/path/a.md")
        eq.dequeue(evt["id"])
        result = eq.list_events(include_processed=True)
        assert "queue" in result
        assert "processed" in result
        assert len(result["processed"]) == 1
