#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
會話清理優化測試
================

測試 WebFeedbackSession 和 SessionCleanupManager 的清理功能。
"""

import asyncio
import pytest
import time
import threading
from unittest.mock import Mock, patch, MagicMock
from datetime import datetime, timedelta

import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))

from src.mcp_feedback_enhanced.web.models.feedback_session import (
    WebFeedbackSession, SessionStatus, CleanupReason
)
from src.mcp_feedback_enhanced.web.utils.session_cleanup_manager import (
    SessionCleanupManager, CleanupPolicy, CleanupTrigger
)


class TestWebFeedbackSessionCleanup:
    """測試 WebFeedbackSession 清理功能"""
    
    def setup_method(self):
        """測試前設置"""
        self.session_id = "test_session_001"
        self.project_dir = "/tmp/test_project"
        self.summary = "測試會話摘要"
        
        # 創建測試會話
        self.session = WebFeedbackSession(
            self.session_id, 
            self.project_dir, 
            self.summary,
            auto_cleanup_delay=60,  # 1分鐘自動清理
            max_idle_time=30  # 30秒最大空閒時間
        )
    
    def teardown_method(self):
        """測試後清理"""
        if hasattr(self, 'session') and self.session:
            try:
                self.session._cleanup_sync_enhanced(CleanupReason.MANUAL)
            except:
                pass
    
    def test_session_initialization(self):
        """測試會話初始化"""
        assert self.session.session_id == self.session_id
        assert self.session.project_directory == self.project_dir
        assert self.session.summary == self.summary
        assert self.session.status == SessionStatus.WAITING
        assert self.session.auto_cleanup_delay == 60
        assert self.session.max_idle_time == 30
        assert self.session.cleanup_timer is not None
        assert len(self.session.cleanup_stats) > 0
    
    def test_is_expired_by_idle_time(self):
        """測試空閒時間過期檢測"""
        # 新創建的會話不應該過期
        assert not self.session.is_expired()
        
        # 模擬空閒時間過長
        self.session.last_activity = time.time() - 40  # 40秒前
        assert self.session.is_expired()
    
    def test_is_expired_by_status(self):
        """測試狀態過期檢測"""
        # 設置為錯誤狀態
        self.session.status = SessionStatus.ERROR
        self.session.last_activity = time.time() - 400  # 400秒前
        assert self.session.is_expired()
        
        # 設置為已過期狀態
        self.session.status = SessionStatus.EXPIRED
        assert self.session.is_expired()
    
    def test_get_age_and_idle_time(self):
        """測試年齡和空閒時間計算"""
        # 測試年齡
        age = self.session.get_age()
        assert age >= 0
        assert age < 1  # 剛創建，應該小於1秒
        
        # 測試空閒時間
        idle_time = self.session.get_idle_time()
        assert idle_time >= 0
        assert idle_time < 1  # 剛創建，應該小於1秒
    
    def test_cleanup_timer_scheduling(self):
        """測試清理定時器調度"""
        # 檢查定時器是否已設置
        assert self.session.cleanup_timer is not None
        assert self.session.cleanup_timer.is_alive()
        
        # 測試延長定時器
        old_timer = self.session.cleanup_timer
        self.session.extend_cleanup_timer(120)
        
        # 應該創建新的定時器
        assert self.session.cleanup_timer != old_timer
        assert self.session.cleanup_timer.is_alive()
    
    def test_cleanup_callbacks(self):
        """測試清理回調函數"""
        callback_called = False
        callback_session = None
        callback_reason = None
        
        def test_callback(session, reason):
            nonlocal callback_called, callback_session, callback_reason
            callback_called = True
            callback_session = session
            callback_reason = reason
        
        # 添加回調
        self.session.add_cleanup_callback(test_callback)
        assert len(self.session.cleanup_callbacks) == 1
        
        # 執行清理
        self.session._cleanup_sync_enhanced(CleanupReason.MANUAL)
        
        # 檢查回調是否被調用
        assert callback_called
        assert callback_session == self.session
        assert callback_reason == CleanupReason.MANUAL
        
        # 移除回調
        self.session.remove_cleanup_callback(test_callback)
        assert len(self.session.cleanup_callbacks) == 0
    
    def test_cleanup_stats(self):
        """測試清理統計"""
        # 初始統計
        stats = self.session.get_cleanup_stats()
        assert stats["cleanup_count"] == 0
        assert stats["session_id"] == self.session_id
        assert stats["is_active"] == True
        
        # 執行清理
        self.session._cleanup_sync_enhanced(CleanupReason.EXPIRED)
        
        # 檢查統計更新
        stats = self.session.get_cleanup_stats()
        assert stats["cleanup_count"] == 1
        assert stats["cleanup_reason"] == CleanupReason.EXPIRED.value
        assert stats["last_cleanup_time"] is not None
        assert stats["cleanup_duration"] >= 0
    
    @pytest.mark.asyncio
    async def test_async_cleanup(self):
        """測試異步清理"""
        # 模擬 WebSocket 連接
        mock_websocket = Mock()
        mock_websocket.send_json = Mock(return_value=asyncio.Future())
        mock_websocket.send_json.return_value.set_result(None)
        mock_websocket.close = Mock(return_value=asyncio.Future())
        mock_websocket.close.return_value.set_result(None)
        mock_websocket.client_state.DISCONNECTED = False
        
        self.session.websocket = mock_websocket
        
        # 執行異步清理
        await self.session._cleanup_resources_enhanced(CleanupReason.TIMEOUT)
        
        # 檢查 WebSocket 是否被正確處理
        mock_websocket.send_json.assert_called_once()
        
        # 檢查清理統計
        stats = self.session.get_cleanup_stats()
        assert stats["cleanup_count"] == 1
        assert stats["cleanup_reason"] == CleanupReason.TIMEOUT.value
    
    def test_status_update_resets_timer(self):
        """測試狀態更新重置定時器"""
        old_timer = self.session.cleanup_timer
        
        # 更新狀態為活躍
        self.session.update_status(SessionStatus.ACTIVE, "測試活躍狀態")
        
        # 檢查定時器是否被重置
        assert self.session.cleanup_timer != old_timer
        assert self.session.cleanup_timer.is_alive()
        assert self.session.status == SessionStatus.ACTIVE


class TestSessionCleanupManager:
    """測試 SessionCleanupManager 功能"""
    
    def setup_method(self):
        """測試前設置"""
        # 創建模擬的 WebUIManager
        self.mock_web_ui_manager = Mock()
        self.mock_web_ui_manager.sessions = {}
        self.mock_web_ui_manager.current_session = None
        self.mock_web_ui_manager.cleanup_expired_sessions = Mock(return_value=0)
        self.mock_web_ui_manager.cleanup_sessions_by_memory_pressure = Mock(return_value=0)
        
        # 創建清理策略
        self.policy = CleanupPolicy(
            max_idle_time=30,
            max_session_age=300,
            max_sessions=5,
            cleanup_interval=10,
            enable_auto_cleanup=True
        )
        
        # 創建清理管理器
        self.cleanup_manager = SessionCleanupManager(
            self.mock_web_ui_manager,
            self.policy
        )
    
    def teardown_method(self):
        """測試後清理"""
        if hasattr(self, 'cleanup_manager'):
            self.cleanup_manager.stop_auto_cleanup()
    
    def test_cleanup_manager_initialization(self):
        """測試清理管理器初始化"""
        assert self.cleanup_manager.web_ui_manager == self.mock_web_ui_manager
        assert self.cleanup_manager.policy == self.policy
        assert not self.cleanup_manager.is_running
        assert self.cleanup_manager.cleanup_thread is None
        assert len(self.cleanup_manager.cleanup_callbacks) == 0
        assert len(self.cleanup_manager.cleanup_history) == 0
    
    def test_auto_cleanup_start_stop(self):
        """測試自動清理啟動和停止"""
        # 啟動自動清理
        result = self.cleanup_manager.start_auto_cleanup()
        assert result == True
        assert self.cleanup_manager.is_running == True
        assert self.cleanup_manager.cleanup_thread is not None
        assert self.cleanup_manager.cleanup_thread.is_alive()
        
        # 停止自動清理
        result = self.cleanup_manager.stop_auto_cleanup()
        assert result == True
        assert self.cleanup_manager.is_running == False
    
    def test_trigger_cleanup_memory_pressure(self):
        """測試內存壓力清理觸發"""
        # 設置模擬返回值
        self.mock_web_ui_manager.cleanup_sessions_by_memory_pressure.return_value = 3
        
        # 觸發內存壓力清理
        cleaned = self.cleanup_manager.trigger_cleanup(CleanupTrigger.MEMORY_PRESSURE, force=True)
        
        # 檢查結果
        assert cleaned == 3
        self.mock_web_ui_manager.cleanup_sessions_by_memory_pressure.assert_called_once_with(True)
        
        # 檢查統計更新
        stats = self.cleanup_manager.get_cleanup_statistics()
        assert stats["total_cleanups"] == 1
        assert stats["memory_pressure_cleanups"] == 1
        assert stats["total_sessions_cleaned"] == 3
    
    def test_trigger_cleanup_expired(self):
        """測試過期清理觸發"""
        # 設置模擬返回值
        self.mock_web_ui_manager.cleanup_expired_sessions.return_value = 2
        
        # 觸發過期清理
        cleaned = self.cleanup_manager.trigger_cleanup(CleanupTrigger.EXPIRED)
        
        # 檢查結果
        assert cleaned == 2
        self.mock_web_ui_manager.cleanup_expired_sessions.assert_called_once()
        
        # 檢查統計更新
        stats = self.cleanup_manager.get_cleanup_statistics()
        assert stats["total_cleanups"] == 1
        assert stats["expired_cleanups"] == 1
        assert stats["total_sessions_cleaned"] == 2
    
    def test_cleanup_statistics(self):
        """測試清理統計功能"""
        # 初始統計
        stats = self.cleanup_manager.get_cleanup_statistics()
        assert stats["total_cleanups"] == 0
        assert stats["total_sessions_cleaned"] == 0
        assert stats["is_auto_cleanup_running"] == False
        
        # 執行一些清理操作
        self.mock_web_ui_manager.cleanup_expired_sessions.return_value = 1
        self.cleanup_manager.trigger_cleanup(CleanupTrigger.EXPIRED)
        
        self.mock_web_ui_manager.cleanup_sessions_by_memory_pressure.return_value = 2
        self.cleanup_manager.trigger_cleanup(CleanupTrigger.MEMORY_PRESSURE)
        
        # 檢查統計
        stats = self.cleanup_manager.get_cleanup_statistics()
        assert stats["total_cleanups"] == 2
        assert stats["expired_cleanups"] == 1
        assert stats["memory_pressure_cleanups"] == 1
        assert stats["total_sessions_cleaned"] == 3
        assert stats["average_cleanup_time"] >= 0
    
    def test_cleanup_history(self):
        """測試清理歷史記錄"""
        # 初始歷史為空
        history = self.cleanup_manager.get_cleanup_history()
        assert len(history) == 0
        
        # 執行清理操作
        self.mock_web_ui_manager.cleanup_expired_sessions.return_value = 1
        self.cleanup_manager.trigger_cleanup(CleanupTrigger.EXPIRED)
        
        # 檢查歷史記錄
        history = self.cleanup_manager.get_cleanup_history()
        assert len(history) == 1
        
        record = history[0]
        assert record["trigger"] == CleanupTrigger.EXPIRED.value
        assert record["cleaned_count"] == 1
        assert "timestamp" in record
        assert "duration" in record
    
    def test_policy_update(self):
        """測試策略更新"""
        # 更新策略
        self.cleanup_manager.update_policy(
            max_idle_time=60,
            max_sessions=10,
            enable_auto_cleanup=False
        )
        
        # 檢查策略是否更新
        assert self.cleanup_manager.policy.max_idle_time == 60
        assert self.cleanup_manager.policy.max_sessions == 10
        assert self.cleanup_manager.policy.enable_auto_cleanup == False
    
    def test_stats_reset(self):
        """測試統計重置"""
        # 執行一些操作產生統計
        self.mock_web_ui_manager.cleanup_expired_sessions.return_value = 1
        self.cleanup_manager.trigger_cleanup(CleanupTrigger.EXPIRED)
        
        # 檢查有統計數據
        stats = self.cleanup_manager.get_cleanup_statistics()
        assert stats["total_cleanups"] > 0
        
        # 重置統計
        self.cleanup_manager.reset_stats()
        
        # 檢查統計已重置
        stats = self.cleanup_manager.get_cleanup_statistics()
        assert stats["total_cleanups"] == 0
        assert stats["total_sessions_cleaned"] == 0
        
        history = self.cleanup_manager.get_cleanup_history()
        assert len(history) == 0


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