# File: tests/unit/infrastructure/json/mcp/test_performance_monitor.py
# Purpose: Unit tests for PerformanceMonitor class
# Context: Validates performance metrics collection and aggregation

"""Comprehensive unit tests for PerformanceMonitor monitoring system.

Tests cover:
- Execution metrics recording
- Cache metrics tracking
- Performance aggregation
- Optimization suggestions
- History management
- Async monitoring
"""

import asyncio
from unittest.mock import Mock

import pytest

from noveler.infrastructure.json.mcp.servers.performance_monitor import (
    CacheMetrics,
    ExecutionMetrics,
    PerformanceMonitor,
)
from noveler.infrastructure.logging.unified_logger import get_logger


@pytest.fixture
def mock_logger():
    """Create a mock logger for testing."""
    return Mock()


@pytest.fixture
def performance_monitor(mock_logger):
    """Create a PerformanceMonitor instance for testing."""
    return PerformanceMonitor(logger=mock_logger, history_size=10)


class TestExecutionMetrics:
    """Test ExecutionMetrics dataclass."""

    def test_execution_metrics_success(self):
        """Test ExecutionMetrics with successful execution."""
        metric = ExecutionMetrics(
            tool_name="test_tool",
            start_time=1000.0,
            end_time=1100.0,
            duration_ms=100.0,
            status="success",
        )
        assert metric.success is True

    def test_execution_metrics_failure(self):
        """Test ExecutionMetrics with failed execution."""
        metric = ExecutionMetrics(
            tool_name="test_tool",
            start_time=1000.0,
            end_time=1100.0,
            duration_ms=100.0,
            status="failure",
            error="Test error",
        )
        assert metric.success is False
        assert metric.error == "Test error"


class TestCacheMetrics:
    """Test CacheMetrics dataclass."""

    def test_cache_metrics_hit_rate_calculation(self):
        """Test cache hit rate calculation."""
        cache = CacheMetrics(cache_name="TestCache", hits=80, misses=20)
        assert cache.hit_rate == 80.0

    def test_cache_metrics_hit_rate_zero(self):
        """Test cache hit rate when no operations occurred."""
        cache = CacheMetrics(cache_name="TestCache")
        assert cache.hit_rate == 0.0

    def test_cache_metrics_hit_rate_all_misses(self):
        """Test cache hit rate with all misses."""
        cache = CacheMetrics(cache_name="TestCache", hits=0, misses=100)
        assert cache.hit_rate == 0.0


class TestPerformanceMonitorInitialization:
    """Test PerformanceMonitor initialization."""

    def test_monitor_initializes_with_logger(self, mock_logger):
        """Test that monitor initializes with logger."""
        monitor = PerformanceMonitor(logger=mock_logger)
        assert monitor._logger is mock_logger

    def test_monitor_initializes_metrics(self, performance_monitor):
        """Test that metrics are initialized."""
        assert performance_monitor._total_executions == 0
        assert performance_monitor._successful_executions == 0
        assert performance_monitor._failed_executions == 0
        assert len(performance_monitor._cache_metrics) == 3


class TestExecutionMetricsRecording:
    """Test recording of execution metrics."""

    def test_record_successful_execution(self, performance_monitor):
        """Test recording a successful execution."""
        performance_monitor.record_execution(
            tool_name="test_tool",
            duration_ms=100.0,
            status="success",
        )

        assert performance_monitor._total_executions == 1
        assert performance_monitor._successful_executions == 1
        assert performance_monitor._failed_executions == 0

    def test_record_failed_execution(self, performance_monitor):
        """Test recording a failed execution."""
        performance_monitor.record_execution(
            tool_name="test_tool",
            duration_ms=100.0,
            status="failure",
            error="Test error",
        )

        assert performance_monitor._total_executions == 1
        assert performance_monitor._successful_executions == 0
        assert performance_monitor._failed_executions == 1

    def test_record_multiple_executions(self, performance_monitor):
        """Test recording multiple executions."""
        for i in range(5):
            performance_monitor.record_execution(
                tool_name=f"tool_{i}",
                duration_ms=100.0 + i * 10,
                status="success" if i % 2 == 0 else "failure",
            )

        assert performance_monitor._total_executions == 5
        assert performance_monitor._successful_executions == 3
        assert performance_monitor._failed_executions == 2

    def test_execution_history_pruning(self, performance_monitor):
        """Test that execution history is pruned when exceeding size."""
        # Record more than history_size (10) executions
        for i in range(15):
            performance_monitor.record_execution(
                tool_name="test_tool",
                duration_ms=100.0,
                status="success",
            )

        # History should be limited to history_size
        assert len(performance_monitor._execution_history) == 10


class TestCacheMetricsRecording:
    """Test cache metrics recording."""

    def test_record_cache_hit(self, performance_monitor):
        """Test recording a cache hit."""
        performance_monitor.record_cache_hit("FileIOCache")

        assert performance_monitor._cache_metrics["FileIOCache"].hits == 1
        assert performance_monitor._cache_metrics["FileIOCache"].misses == 0

    def test_record_cache_miss(self, performance_monitor):
        """Test recording a cache miss."""
        performance_monitor.record_cache_miss("FileIOCache")

        assert performance_monitor._cache_metrics["FileIOCache"].hits == 0
        assert performance_monitor._cache_metrics["FileIOCache"].misses == 1

    def test_record_cache_eviction(self, performance_monitor):
        """Test recording cache evictions."""
        performance_monitor.record_cache_eviction("JSONCache", count=5)

        assert performance_monitor._cache_metrics["JSONCache"].evictions == 5

    def test_update_cache_size(self, performance_monitor):
        """Test updating cache size."""
        performance_monitor.update_cache_size("PathCache", size=42)

        assert performance_monitor._cache_metrics["PathCache"].total_entries == 42


class TestMetricsAggregation:
    """Test metrics aggregation and reporting."""

    def test_get_metrics_empty(self, performance_monitor):
        """Test getting metrics when no executions recorded."""
        metrics = performance_monitor.get_metrics()

        assert metrics.total_executions == 0
        assert metrics.success_rate == 0.0
        assert metrics.average_execution_time_ms == 0.0

    def test_get_metrics_with_executions(self, performance_monitor):
        """Test getting metrics after recording executions."""
        performance_monitor.record_execution("tool1", 100.0, "success")
        performance_monitor.record_execution("tool2", 200.0, "failure")

        metrics = performance_monitor.get_metrics()

        assert metrics.total_executions == 2
        assert metrics.successful_executions == 1
        assert metrics.failed_executions == 1
        assert metrics.success_rate == 50.0
        assert metrics.average_execution_time_ms == 150.0

    def test_get_metrics_includes_suggestions(self, performance_monitor):
        """Test that metrics include optimization suggestions."""
        # Record slow executions
        for _ in range(10):
            performance_monitor.record_execution("slow_tool", 5000.0, "failure")

        metrics = performance_monitor.get_metrics()

        assert len(metrics.optimization_suggestions) > 0


class TestOptimizationSuggestions:
    """Test optimization suggestion generation."""

    def test_slow_execution_suggestion(self, performance_monitor):
        """Test suggestion for slow executions."""
        # Record very slow execution
        performance_monitor.record_execution("slow_tool", 2000.0, "success")

        metrics = performance_monitor.get_metrics()

        assert any("平均実行時間が長い" in s for s in metrics.optimization_suggestions)

    def test_low_success_rate_suggestion(self, performance_monitor):
        """Test suggestion for low success rate."""
        # Record mostly failed executions
        for _ in range(10):
            performance_monitor.record_execution("unreliable", 100.0, "failure")

        metrics = performance_monitor.get_metrics()

        assert any("成功率が低い" in s for s in metrics.optimization_suggestions)

    def test_cache_eviction_suggestion(self, performance_monitor):
        """Test suggestion for high eviction rate."""
        # Record high evictions
        performance_monitor.update_cache_size("FileIOCache", 10)
        performance_monitor.record_cache_eviction("FileIOCache", count=150)

        metrics = performance_monitor.get_metrics()

        assert any("削除が多い" in s for s in metrics.optimization_suggestions)


class TestAsyncMonitoring:
    """Test async monitoring functionality."""

    @pytest.mark.asyncio
    async def test_start_monitoring_can_be_cancelled(self, performance_monitor):
        """Test that monitoring can be started and cancelled."""
        monitor_task = asyncio.create_task(performance_monitor.start_monitoring())

        # Let it run for a moment
        await asyncio.sleep(0.1)

        # Cancel monitoring
        monitor_task.cancel()

        try:
            await monitor_task
        except asyncio.CancelledError:
            pass  # Expected

        # Should complete without error


class TestPerformanceSummary:
    """Test performance summary generation."""

    def test_get_summary_empty(self, performance_monitor):
        """Test summary when no metrics recorded."""
        summary = performance_monitor.get_summary()

        assert summary["total_executions"] == 0
        assert summary["success_rate_percent"] == 0.0
        assert summary["average_execution_time_ms"] == 0.0

    def test_get_summary_with_metrics(self, performance_monitor):
        """Test summary with recorded metrics."""
        performance_monitor.record_execution("tool1", 150.0, "success")
        performance_monitor.record_cache_hit("FileIOCache")
        performance_monitor.record_cache_miss("JSONCache")

        summary = performance_monitor.get_summary()

        assert summary["total_executions"] == 1
        assert summary["success_rate_percent"] == 100.0
        assert summary["average_execution_time_ms"] == 150.0
        assert "FileIOCache" in summary["cache_hit_rates"]


class TestPerformanceMonitorIntegration:
    """Integration tests with real logger."""

    def test_performance_monitor_with_real_logger(self):
        """Test PerformanceMonitor with real logger."""
        logger = get_logger(__name__)
        monitor = PerformanceMonitor(logger=logger)

        monitor.record_execution("test_tool", 100.0, "success")
        monitor.record_cache_hit("FileIOCache")

        metrics = monitor.get_metrics()

        assert metrics.total_executions == 1
        assert metrics.cache_metrics["FileIOCache"].hits == 1

    def test_performance_monitor_full_workflow(self):
        """Test complete performance monitoring workflow."""
        logger = get_logger(__name__)
        monitor = PerformanceMonitor(logger=logger, history_size=50)

        # Simulate tool execution sequence
        for i in range(10):
            monitor.record_execution(
                tool_name=f"tool_{i % 3}",
                duration_ms=50.0 + i * 10,
                status="success" if i % 3 != 0 else "failure",
            )

            # Simulate cache operations
            if i % 2 == 0:
                monitor.record_cache_hit("FileIOCache")
            else:
                monitor.record_cache_miss("JSONCache")

        # Update cache sizes
        monitor.update_cache_size("FileIOCache", 250)
        monitor.update_cache_size("JSONCache", 95)

        # Get metrics
        metrics = monitor.get_metrics()
        summary = monitor.get_summary()

        assert metrics.total_executions == 10
        assert "timestamp" in summary
        assert "cache_hit_rates" in summary
