"""
tests/test_session_auto_compress.py

SessionAutoCompress 단위 테스트 (TDD - RED 단계)

테스트 항목:
- auto_compress: 메시지 리스트 압축 → 반환 메시지 수 감소 확인
- auto_compress: 이벤트 파일 생성 확인 + JSON 형식 검증
- auto_compress: monitor.reset() 호출 확인
- save_session_summary: 파일 생성 + 경로 반환 확인
- save_session_summary: 파일 내용에 task_id, team_id, modified_files 등 필수 필드 포함 확인
- save_session_summary: 빈 메시지 → 에러 없이 처리
- setup_auto_hooks: 콜백 등록 확인
"""

from __future__ import annotations

import json
import os
import sys
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

_WORKSPACE = Path(os.environ.get("WORKSPACE_ROOT", "/home/jay/workspace"))
if str(_WORKSPACE) not in sys.path:
    sys.path.insert(0, str(_WORKSPACE))

from utils.session_monitor import SessionMonitor  # noqa: E402

# ---------------------------------------------------------------------------
# 헬퍼: 더미 메시지 생성
# ---------------------------------------------------------------------------


def _make_messages(count: int = 20) -> list[dict]:
    """테스트용 메시지 리스트를 생성한다. 압축이 일어날 만큼 충분한 크기."""
    msgs: list[dict] = []
    for i in range(count):
        if i % 2 == 0:
            msgs.append({"role": "user", "content": f"질문 {i}: " + "x" * 500})
        else:
            msgs.append({"role": "assistant", "content": f"답변 {i}: " + "y" * 500})
    return msgs


# ---------------------------------------------------------------------------
# 1. auto_compress: 메시지 압축 기능
# ---------------------------------------------------------------------------


class TestAutoCompress:
    """auto_compress()가 올바르게 메시지를 압축하는지 확인"""

    def test_auto_compress_returns_tuple(self, tmp_path):
        """auto_compress가 (list, dict) 튜플을 반환해야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=tmp_path / "events",
            sessions_dir=tmp_path / "sessions",
        )
        messages = _make_messages(20)
        result = sac.auto_compress(messages)
        assert isinstance(result, tuple)
        assert len(result) == 2
        compressed_msgs, event_info = result
        assert isinstance(compressed_msgs, list)
        assert isinstance(event_info, dict)

    def test_auto_compress_reduces_message_count(self, tmp_path):
        """압축 후 메시지 수가 원본보다 적거나 같아야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=tmp_path / "events",
            sessions_dir=tmp_path / "sessions",
        )
        # 많은 메시지로 압축이 확실히 일어나도록
        messages = _make_messages(30)
        compressed_msgs, _ = sac.auto_compress(messages)
        # 압축기는 중간 구역을 요약 1개 메시지로 줄이므로 감소해야 함
        assert len(compressed_msgs) <= len(messages)

    def test_auto_compress_event_file_created(self, tmp_path):
        """압축 후 events_dir에 이벤트 파일이 생성되어야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        events_dir = tmp_path / "events"
        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=events_dir,
            sessions_dir=tmp_path / "sessions",
        )
        messages = _make_messages(10)
        sac.auto_compress(messages)

        event_files = list(events_dir.glob("session-compressed-*.json"))
        assert len(event_files) == 1

    def test_auto_compress_event_file_json_format(self, tmp_path):
        """이벤트 파일이 올바른 JSON 형식과 필수 키를 포함해야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        events_dir = tmp_path / "events"
        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=events_dir,
            sessions_dir=tmp_path / "sessions",
        )
        messages = _make_messages(10)
        sac.auto_compress(messages)

        event_files = list(events_dir.glob("session-compressed-*.json"))
        assert len(event_files) == 1

        with open(event_files[0], encoding="utf-8") as f:
            data = json.load(f)

        assert data["event"] == "session_compressed"
        assert "timestamp" in data
        assert "before_count" in data
        assert "after_count" in data
        assert "tokens_before" in data
        assert "tokens_after" in data
        assert data["before_count"] == len(messages)

    def test_auto_compress_monitor_reset_called(self, tmp_path):
        """압축 후 monitor.reset()이 호출되어야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        # reset에 큰 값을 넣어 두어 리셋 후 변화를 확인
        monitor.reset(new_total=180_000)

        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=tmp_path / "events",
            sessions_dir=tmp_path / "sessions",
        )
        messages = _make_messages(10)
        sac.auto_compress(messages)

        # 리셋 후 total_tokens는 압축된 메시지 기반 추정값으로 줄어야 함
        status = monitor.get_usage_status()
        assert status["total_tokens"] < 180_000

    def test_auto_compress_empty_messages(self, tmp_path):
        """빈 메시지 리스트도 에러 없이 처리되어야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=tmp_path / "events",
            sessions_dir=tmp_path / "sessions",
        )
        compressed_msgs, event_info = sac.auto_compress([])
        assert compressed_msgs == []
        assert isinstance(event_info, dict)

    def test_auto_compress_event_info_in_return(self, tmp_path):
        """반환된 event_info dict에 필수 키가 포함되어야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=tmp_path / "events",
            sessions_dir=tmp_path / "sessions",
        )
        messages = _make_messages(10)
        _, event_info = sac.auto_compress(messages)

        assert "event" in event_info
        assert event_info["event"] == "session_compressed"
        assert "before_count" in event_info
        assert "after_count" in event_info


# ---------------------------------------------------------------------------
# 2. save_session_summary: 세션 요약 저장
# ---------------------------------------------------------------------------


class TestSaveSessionSummary:
    """save_session_summary()가 올바르게 요약 파일을 저장하는지 확인"""

    def test_save_session_summary_returns_path(self, tmp_path):
        """save_session_summary가 Path 객체를 반환해야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sessions_dir = tmp_path / "sessions"
        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=tmp_path / "events",
            sessions_dir=sessions_dir,
        )
        messages = _make_messages(5)
        result = sac.save_session_summary(
            messages=messages,
            task_id="task-100.1",
            team_id="dev6-team",
        )
        assert isinstance(result, Path)

    def test_save_session_summary_file_exists(self, tmp_path):
        """save_session_summary 호출 후 파일이 실제로 생성되어야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sessions_dir = tmp_path / "sessions"
        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=tmp_path / "events",
            sessions_dir=sessions_dir,
        )
        messages = _make_messages(5)
        path = sac.save_session_summary(
            messages=messages,
            task_id="task-100.1",
            team_id="dev6-team",
        )
        assert path.exists()
        assert path.suffix == ".md"

    def test_save_session_summary_filename_contains_task_id(self, tmp_path):
        """저장 파일 이름에 task_id가 포함되어야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sessions_dir = tmp_path / "sessions"
        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=tmp_path / "events",
            sessions_dir=sessions_dir,
        )
        messages = _make_messages(5)
        path = sac.save_session_summary(
            messages=messages,
            task_id="task-999.9",
            team_id="dev6-team",
        )
        assert "task-999.9" in path.name

    def test_save_session_summary_content_has_task_id(self, tmp_path):
        """파일 내용에 task_id가 포함되어야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sessions_dir = tmp_path / "sessions"
        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=tmp_path / "events",
            sessions_dir=sessions_dir,
        )
        messages = _make_messages(5)
        path = sac.save_session_summary(
            messages=messages,
            task_id="task-100.1",
            team_id="dev6-team",
        )
        content = path.read_text(encoding="utf-8")
        assert "task-100.1" in content

    def test_save_session_summary_content_has_team_id(self, tmp_path):
        """파일 내용에 team_id가 포함되어야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sessions_dir = tmp_path / "sessions"
        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=tmp_path / "events",
            sessions_dir=sessions_dir,
        )
        messages = _make_messages(5)
        path = sac.save_session_summary(
            messages=messages,
            task_id="task-100.1",
            team_id="dev6-team",
        )
        content = path.read_text(encoding="utf-8")
        assert "dev6-team" in content

    def test_save_session_summary_content_has_modified_files(self, tmp_path):
        """파일 내용에 modified_files 목록이 포함되어야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sessions_dir = tmp_path / "sessions"
        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=tmp_path / "events",
            sessions_dir=sessions_dir,
        )
        messages = _make_messages(5)
        path = sac.save_session_summary(
            messages=messages,
            task_id="task-100.1",
            team_id="dev6-team",
            modified_files=["utils/foo.py", "utils/bar.py"],
        )
        content = path.read_text(encoding="utf-8")
        assert "utils/foo.py" in content
        assert "utils/bar.py" in content

    def test_save_session_summary_content_has_remaining_tasks(self, tmp_path):
        """파일 내용에 remaining_tasks 목록이 포함되어야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sessions_dir = tmp_path / "sessions"
        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=tmp_path / "events",
            sessions_dir=sessions_dir,
        )
        messages = _make_messages(5)
        path = sac.save_session_summary(
            messages=messages,
            task_id="task-100.1",
            team_id="dev6-team",
            remaining_tasks=["작업A 완료", "작업B 시작"],
        )
        content = path.read_text(encoding="utf-8")
        assert "작업A 완료" in content
        assert "작업B 시작" in content

    def test_save_session_summary_content_has_last_success_step(self, tmp_path):
        """파일 내용에 last_success_step이 포함되어야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sessions_dir = tmp_path / "sessions"
        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=tmp_path / "events",
            sessions_dir=sessions_dir,
        )
        messages = _make_messages(5)
        path = sac.save_session_summary(
            messages=messages,
            task_id="task-100.1",
            team_id="dev6-team",
            last_success_step="테스트 통과 완료",
        )
        content = path.read_text(encoding="utf-8")
        assert "테스트 통과 완료" in content

    def test_save_session_summary_content_has_errors(self, tmp_path):
        """파일 내용에 errors 목록이 포함되어야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sessions_dir = tmp_path / "sessions"
        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=tmp_path / "events",
            sessions_dir=sessions_dir,
        )
        messages = _make_messages(5)
        path = sac.save_session_summary(
            messages=messages,
            task_id="task-100.1",
            team_id="dev6-team",
            errors=["ImportError: module not found", "TypeError: expected str"],
        )
        content = path.read_text(encoding="utf-8")
        assert "ImportError: module not found" in content
        assert "TypeError: expected str" in content

    def test_save_session_summary_empty_messages(self, tmp_path):
        """빈 메시지 리스트도 에러 없이 처리되어야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sessions_dir = tmp_path / "sessions"
        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=tmp_path / "events",
            sessions_dir=sessions_dir,
        )
        # 빈 메시지 → 에러 없이 처리
        path = sac.save_session_summary(
            messages=[],
            task_id="task-empty.1",
            team_id="dev6-team",
        )
        assert path.exists()
        content = path.read_text(encoding="utf-8")
        assert "task-empty.1" in content

    def test_save_session_summary_none_optional_fields(self, tmp_path):
        """optional 필드가 None일 때도 에러 없이 처리되어야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sessions_dir = tmp_path / "sessions"
        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=tmp_path / "events",
            sessions_dir=sessions_dir,
        )
        messages = _make_messages(3)
        path = sac.save_session_summary(
            messages=messages,
            task_id="task-opt.1",
            team_id="dev1-team",
            modified_files=None,
            remaining_tasks=None,
            last_success_step=None,
            errors=None,
        )
        assert path.exists()

    def test_save_session_summary_markdown_headers(self, tmp_path):
        """파일 내용에 마크다운 헤더 구조가 있어야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sessions_dir = tmp_path / "sessions"
        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=tmp_path / "events",
            sessions_dir=sessions_dir,
        )
        messages = _make_messages(5)
        path = sac.save_session_summary(
            messages=messages,
            task_id="task-100.1",
            team_id="dev6-team",
        )
        content = path.read_text(encoding="utf-8")
        assert "# 세션 요약" in content
        assert "## 기본 정보" in content
        assert "## 자동 요약" in content


# ---------------------------------------------------------------------------
# 3. setup_auto_hooks: 콜백 등록
# ---------------------------------------------------------------------------


class TestSetupAutoHooks:
    """setup_auto_hooks()가 콜백을 올바르게 등록하는지 확인"""

    def test_setup_auto_hooks_registers_callback(self, tmp_path):
        """setup_auto_hooks 호출 후 monitor에 critical 콜백이 등록되어야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=tmp_path / "events",
            sessions_dir=tmp_path / "sessions",
        )
        # 등록 전 콜백 수
        before_count = len(monitor._callbacks.get("critical", []))
        sac.setup_auto_hooks()
        after_count = len(monitor._callbacks.get("critical", []))
        assert after_count == before_count + 1

    def test_setup_auto_hooks_callback_is_callable(self, tmp_path):
        """등록된 콜백이 callable이어야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=tmp_path / "events",
            sessions_dir=tmp_path / "sessions",
        )
        sac.setup_auto_hooks()
        callbacks = monitor._callbacks.get("critical", [])
        assert len(callbacks) > 0
        assert callable(callbacks[-1])

    def test_setup_auto_hooks_callback_invoked_on_critical(self, tmp_path):
        """critical 레벨 도달 시 등록된 콜백이 호출되어야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=tmp_path / "events",
            sessions_dir=tmp_path / "sessions",
        )
        sac.setup_auto_hooks()

        # 85% = 170_000 토큰 → critical 전환 → 콜백 호출
        # _on_critical 내부에서 로그만 남기므로 예외 없이 실행되면 됨
        try:
            monitor.update({"input_tokens": 170_000, "output_tokens": 0})
        except Exception as exc:
            pytest.fail(f"critical 콜백 실행 중 예외 발생: {exc}")

    def test_setup_auto_hooks_idempotent(self, tmp_path):
        """setup_auto_hooks를 두 번 호출하면 콜백이 두 번 등록되어야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sac = SessionAutoCompress(
            monitor=monitor,
            context_limit=200_000,
            events_dir=tmp_path / "events",
            sessions_dir=tmp_path / "sessions",
        )
        before_count = len(monitor._callbacks.get("critical", []))
        sac.setup_auto_hooks()
        sac.setup_auto_hooks()
        after_count = len(monitor._callbacks.get("critical", []))
        # 두 번 호출하면 두 개의 콜백이 등록됨
        assert after_count == before_count + 2


# ---------------------------------------------------------------------------
# 4. 기본 경로 설정 (WORKSPACE 기반)
# ---------------------------------------------------------------------------


class TestDefaultPaths:
    """기본 경로가 WORKSPACE/memory/events/, WORKSPACE/memory/sessions/으로 설정되는지 확인"""

    def test_default_events_dir(self):
        """events_dir 기본값이 WORKSPACE/memory/events/여야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sac = SessionAutoCompress(monitor=monitor)
        workspace = Path(os.environ.get("WORKSPACE_ROOT", "/home/jay/workspace"))
        assert sac.events_dir == workspace / "memory" / "events"

    def test_default_sessions_dir(self):
        """sessions_dir 기본값이 WORKSPACE/memory/sessions/여야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sac = SessionAutoCompress(monitor=monitor)
        workspace = Path(os.environ.get("WORKSPACE_ROOT", "/home/jay/workspace"))
        assert sac.sessions_dir == workspace / "memory" / "sessions"

    def test_custom_dirs_accepted(self, tmp_path):
        """커스텀 경로가 올바르게 설정되어야 한다."""
        from utils.session_auto_compress import SessionAutoCompress

        monitor = SessionMonitor(context_limit=200_000)
        sac = SessionAutoCompress(
            monitor=monitor,
            events_dir=tmp_path / "my_events",
            sessions_dir=tmp_path / "my_sessions",
        )
        assert sac.events_dir == tmp_path / "my_events"
        assert sac.sessions_dir == tmp_path / "my_sessions"


# ---------------------------------------------------------------------------
# N. _estimate_tokens_for_messages 한국어 보정 통합 테스트
# ---------------------------------------------------------------------------


class TestEstimateTokensKoreanIntegration:
    """session_auto_compress의 토큰 추정이 한국어 보정을 사용하는지 확인"""

    def test_korean_messages_estimated_higher(self):
        """한국어 메시지가 같은 길이의 영어 메시지보다 더 많은 토큰으로 추정된다."""
        from utils.session_auto_compress import _estimate_tokens_for_messages

        ko_text = "안녕하세요 테스트입니다 " * 20
        en_text = "a" * len(ko_text)  # 같은 길이
        ko_msgs = [{"role": "user", "content": ko_text}]
        en_msgs = [{"role": "user", "content": en_text}]

        ko_tokens = _estimate_tokens_for_messages(ko_msgs)
        en_tokens = _estimate_tokens_for_messages(en_msgs)
        # 한국어는 chars/2.5, 영어는 chars/4이므로 한국어 토큰 수가 더 많아야 함
        assert ko_tokens > en_tokens
