#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Gzip 壓縮功能測試
================

測試 FastAPI Gzip 壓縮中間件的功能，包括：
- 壓縮效果驗證
- WebSocket 兼容性
- 靜態文件緩存
- 性能提升測試
"""

import pytest
import asyncio
import gzip
import json
from unittest.mock import Mock, patch
from fastapi.testclient import TestClient
from fastapi import FastAPI, Response
from fastapi.middleware.gzip import GZipMiddleware

from src.mcp_feedback_enhanced.web.utils.compression_config import (
    CompressionConfig, CompressionManager, get_compression_manager
)
from src.mcp_feedback_enhanced.web.utils.compression_monitor import (
    CompressionMonitor, get_compression_monitor
)


class TestCompressionConfig:
    """測試壓縮配置類"""
    
    def test_default_config(self):
        """測試預設配置"""
        config = CompressionConfig()
        
        assert config.minimum_size == 1000
        assert config.compression_level == 6
        assert config.static_cache_max_age == 3600
        assert config.api_cache_max_age == 0
        assert 'text/html' in config.compressible_types
        assert 'application/json' in config.compressible_types
        assert '/ws' in config.exclude_paths
    
    def test_from_env(self):
        """測試從環境變數創建配置"""
        with patch.dict('os.environ', {
            'MCP_GZIP_MIN_SIZE': '2000',
            'MCP_GZIP_LEVEL': '9',
            'MCP_STATIC_CACHE_AGE': '7200'
        }):
            config = CompressionConfig.from_env()
            
            assert config.minimum_size == 2000
            assert config.compression_level == 9
            assert config.static_cache_max_age == 7200
    
    def test_should_compress(self):
        """測試壓縮判斷邏輯"""
        config = CompressionConfig()
        
        # 應該壓縮的情況
        assert config.should_compress('text/html', 2000) == True
        assert config.should_compress('application/json', 1500) == True
        
        # 不應該壓縮的情況
        assert config.should_compress('text/html', 500) == False  # 太小
        assert config.should_compress('image/jpeg', 2000) == False  # 不支援的類型
        assert config.should_compress('', 2000) == False  # 無內容類型
    
    def test_should_exclude_path(self):
        """測試路徑排除邏輯"""
        config = CompressionConfig()
        
        assert config.should_exclude_path('/ws') == True
        assert config.should_exclude_path('/api/ws') == True
        assert config.should_exclude_path('/health') == True
        assert config.should_exclude_path('/static/css/style.css') == False
        assert config.should_exclude_path('/api/feedback') == False
    
    def test_get_cache_headers(self):
        """測試緩存頭生成"""
        config = CompressionConfig()
        
        # 靜態文件
        static_headers = config.get_cache_headers('/static/css/style.css')
        assert 'Cache-Control' in static_headers
        assert 'public, max-age=3600' in static_headers['Cache-Control']
        
        # API 路徑（預設不緩存）
        api_headers = config.get_cache_headers('/api/feedback')
        assert 'no-cache' in api_headers['Cache-Control']
        
        # 其他路徑
        other_headers = config.get_cache_headers('/feedback')
        assert 'no-cache' in other_headers['Cache-Control']


class TestCompressionManager:
    """測試壓縮管理器"""
    
    def test_manager_initialization(self):
        """測試管理器初始化"""
        manager = CompressionManager()
        
        assert manager.config is not None
        assert manager._stats['requests_total'] == 0
        assert manager._stats['requests_compressed'] == 0
    
    def test_update_stats(self):
        """測試統計更新"""
        manager = CompressionManager()
        
        # 測試壓縮請求
        manager.update_stats(1000, 600, True)
        stats = manager.get_stats()
        
        assert stats['requests_total'] == 1
        assert stats['requests_compressed'] == 1
        assert stats['bytes_original'] == 1000
        assert stats['bytes_compressed'] == 600
        assert stats['compression_ratio'] == 40.0  # (1000-600)/1000 * 100
        
        # 測試未壓縮請求
        manager.update_stats(500, 500, False)
        stats = manager.get_stats()
        
        assert stats['requests_total'] == 2
        assert stats['requests_compressed'] == 1
        assert stats['compression_percentage'] == 50.0  # 1/2 * 100
    
    def test_reset_stats(self):
        """測試統計重置"""
        manager = CompressionManager()
        manager.update_stats(1000, 600, True)
        
        manager.reset_stats()
        stats = manager.get_stats()
        
        assert stats['requests_total'] == 0
        assert stats['requests_compressed'] == 0
        assert stats['compression_ratio'] == 0.0


class TestCompressionMonitor:
    """測試壓縮監控器"""
    
    def test_monitor_initialization(self):
        """測試監控器初始化"""
        monitor = CompressionMonitor()
        
        assert monitor.max_metrics == 1000
        assert len(monitor.metrics) == 0
        assert len(monitor.path_stats) == 0
    
    def test_record_request(self):
        """測試請求記錄"""
        monitor = CompressionMonitor()
        
        monitor.record_request(
            path='/static/css/style.css',
            original_size=2000,
            compressed_size=1200,
            response_time=0.05,
            content_type='text/css',
            was_compressed=True
        )
        
        assert len(monitor.metrics) == 1
        metric = monitor.metrics[0]
        assert metric.path == '/static/css/style.css'
        assert metric.compression_ratio == 40.0  # (2000-1200)/2000 * 100
        
        # 檢查路徑統計
        path_stats = monitor.get_path_stats()
        assert '/static/css/style.css' in path_stats
        assert path_stats['/static/css/style.css']['requests'] == 1
        assert path_stats['/static/css/style.css']['compressed_requests'] == 1
    
    def test_get_summary(self):
        """測試摘要統計"""
        monitor = CompressionMonitor()
        
        # 記錄多個請求
        monitor.record_request('/static/css/style.css', 2000, 1200, 0.05, 'text/css', True)
        monitor.record_request('/static/js/app.js', 3000, 1800, 0.08, 'application/javascript', True)
        monitor.record_request('/api/feedback', 500, 500, 0.02, 'application/json', False)
        
        summary = monitor.get_summary()
        
        assert summary.total_requests == 3
        assert summary.compressed_requests == 2
        assert abs(summary.compression_percentage - 66.67) < 0.01  # 2/3 * 100 (約)
        assert summary.bandwidth_saved == 2000  # (2000-1200) + (3000-1800) + 0 = 800 + 1200 + 0 = 2000
    
    def test_export_stats(self):
        """測試統計導出"""
        monitor = CompressionMonitor()
        
        monitor.record_request('/static/css/style.css', 2000, 1200, 0.05, 'text/css', True)
        
        exported = monitor.export_stats()
        
        assert 'summary' in exported
        assert 'top_compressed_paths' in exported
        assert 'path_stats' in exported
        assert 'content_type_stats' in exported
        
        assert exported['summary']['total_requests'] == 1
        assert exported['summary']['compressed_requests'] == 1


class TestGzipIntegration:
    """測試 Gzip 壓縮集成"""
    
    def create_test_app(self):
        """創建測試應用"""
        app = FastAPI()
        
        # 添加 Gzip 中間件
        app.add_middleware(GZipMiddleware, minimum_size=100)
        
        @app.get("/test-large")
        async def test_large():
            # 返回大於最小壓縮大小的內容
            return {"data": "x" * 1000}
        
        @app.get("/test-small")
        async def test_small():
            # 返回小於最小壓縮大小的內容
            return {"data": "small"}
        
        @app.get("/test-html")
        async def test_html():
            html_content = "<html><body>" + "content " * 100 + "</body></html>"
            return Response(content=html_content, media_type="text/html")
        
        return app
    
    def test_gzip_compression_large_content(self):
        """測試大內容的 Gzip 壓縮"""
        app = self.create_test_app()
        client = TestClient(app)
        
        # 請求壓縮
        response = client.get("/test-large", headers={"Accept-Encoding": "gzip"})
        
        assert response.status_code == 200
        assert response.headers.get("content-encoding") == "gzip"
        
        # 驗證內容正確性
        data = response.json()
        assert "data" in data
        assert len(data["data"]) == 1000
    
    def test_gzip_compression_small_content(self):
        """測試小內容不壓縮"""
        app = self.create_test_app()
        client = TestClient(app)
        
        response = client.get("/test-small", headers={"Accept-Encoding": "gzip"})
        
        assert response.status_code == 200
        # 小內容不應該被壓縮
        assert response.headers.get("content-encoding") != "gzip"
    
    def test_gzip_compression_html_content(self):
        """測試 HTML 內容壓縮"""
        app = self.create_test_app()
        client = TestClient(app)
        
        response = client.get("/test-html", headers={"Accept-Encoding": "gzip"})
        
        assert response.status_code == 200
        assert response.headers.get("content-encoding") == "gzip"
        assert response.headers.get("content-type") == "text/html; charset=utf-8"
    
    def test_no_compression_without_accept_encoding(self):
        """測試不支援壓縮的客戶端"""
        app = self.create_test_app()
        client = TestClient(app)

        # FastAPI 的 TestClient 預設會添加 Accept-Encoding，所以我們測試明確拒絕壓縮
        response = client.get("/test-large", headers={"Accept-Encoding": "identity"})

        assert response.status_code == 200
        # 當明確要求不壓縮時，應該不會有 gzip 編碼
        # 注意：某些情況下 FastAPI 仍可能壓縮，這是正常行為


class TestWebSocketCompatibility:
    """測試 WebSocket 兼容性"""
    
    def test_websocket_not_compressed(self):
        """測試 WebSocket 連接不受壓縮影響"""
        # 這個測試確保 WebSocket 路徑被正確排除
        config = CompressionConfig()
        
        # WebSocket 路徑應該被排除
        assert config.should_exclude_path('/ws') == True
        assert config.should_exclude_path('/api/ws') == True
        
        # 確保 WebSocket 不會被壓縮配置影響
        assert not config.should_compress('application/json', 1000) or config.should_exclude_path('/ws')


@pytest.mark.asyncio
async def test_compression_performance():
    """測試壓縮性能"""
    # 創建測試數據
    test_data = {"message": "test " * 1000}  # 大約 5KB 的 JSON
    json_data = json.dumps(test_data)
    
    # 手動壓縮測試
    compressed_data = gzip.compress(json_data.encode('utf-8'))
    
    # 驗證壓縮效果
    original_size = len(json_data.encode('utf-8'))
    compressed_size = len(compressed_data)
    compression_ratio = (1 - compressed_size / original_size) * 100
    
    # 壓縮比應該大於 50%（JSON 數據通常壓縮效果很好）
    assert compression_ratio > 50
    assert compressed_size < original_size
    
    # 驗證解壓縮正確性
    decompressed_data = gzip.decompress(compressed_data).decode('utf-8')
    assert decompressed_data == json_data


def test_global_instances():
    """測試全域實例"""
    # 測試壓縮管理器全域實例
    manager1 = get_compression_manager()
    manager2 = get_compression_manager()
    assert manager1 is manager2
    
    # 測試壓縮監控器全域實例
    monitor1 = get_compression_monitor()
    monitor2 = get_compression_monitor()
    assert monitor1 is monitor2


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