# File: src/noveler/infrastructure/json/mcp/servers/performance_monitor.py
# Purpose: Performance monitoring and metrics collection for JSONConversionServer
# Context: Extracted from JSONConversionServer to improve SRP (Tier 1b Phase 4)

"""Performance monitoring system for tracking execution metrics and optimization opportunities.

This module provides centralized performance monitoring that tracks:
- Tool execution performance
- Cache hit rates and evictions
- Memory usage patterns
- Latency measurements
- Optimization opportunities

Components:
- PerformanceMetrics: Standardized metrics container
- PerformanceMonitor: Central monitoring coordinator
- MetricsAggregator: Metrics aggregation and reporting
"""

from __future__ import annotations

import asyncio
import logging
import time
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any


@dataclass
class ExecutionMetrics:
    """Metrics for a single tool execution.

    Attributes:
        tool_name: Name of the tool executed
        start_time: Execution start timestamp
        end_time: Execution end timestamp
        duration_ms: Execution duration in milliseconds
        status: Execution status (success, failure, timeout)
        error: Error message if execution failed
    """

    tool_name: str
    start_time: float
    end_time: float
    duration_ms: float
    status: str
    error: str | None = None

    @property
    def success(self) -> bool:
        """Return True if execution was successful."""
        return self.status == "success"


@dataclass
class CacheMetrics:
    """Metrics for cache operations.

    Attributes:
        cache_name: Name of the cache (FileIOCache, JSONCache, PathCache)
        hits: Number of cache hits
        misses: Number of cache misses
        evictions: Number of items evicted
        total_entries: Current number of entries in cache
    """

    cache_name: str
    hits: int = 0
    misses: int = 0
    evictions: int = 0
    total_entries: int = 0

    @property
    def hit_rate(self) -> float:
        """Calculate cache hit rate."""
        total = self.hits + self.misses
        return (self.hits / total * 100) if total > 0 else 0.0


@dataclass
class PerformanceMetrics:
    """Aggregated performance metrics container.

    Attributes:
        timestamp: Metrics collection timestamp
        total_executions: Total number of tool executions
        successful_executions: Number of successful executions
        failed_executions: Number of failed executions
        average_execution_time_ms: Average execution duration
        total_execution_time_ms: Total execution duration
        cache_metrics: Per-cache metrics
        peak_memory_mb: Peak memory usage in MB
        optimization_suggestions: List of optimization suggestions
    """

    timestamp: datetime = field(default_factory=datetime.now)
    total_executions: int = 0
    successful_executions: int = 0
    failed_executions: int = 0
    average_execution_time_ms: float = 0.0
    total_execution_time_ms: float = 0.0
    cache_metrics: dict[str, CacheMetrics] = field(default_factory=dict)
    peak_memory_mb: float = 0.0
    optimization_suggestions: list[str] = field(default_factory=list)

    @property
    def success_rate(self) -> float:
        """Calculate execution success rate."""
        if self.total_executions == 0:
            return 0.0
        return (self.successful_executions / self.total_executions) * 100


class PerformanceMonitor:
    """Centralized performance monitoring system.

    Purpose:
        Tracks execution metrics, cache performance, and system health
        to identify optimization opportunities and performance trends.

    Responsibilities:
        - Track execution metrics (duration, status, errors)
        - Monitor cache performance (hit rates, evictions)
        - Aggregate metrics over time
        - Generate optimization suggestions
        - Maintain performance history
        - Provide performance reports

    Context:
        Used by JSONConversionServer to monitor performance and
        generate optimization suggestions for the system.

    Design Patterns:
        - Observer Pattern: Metrics collection without modifying client code
        - Facade Pattern: Unified interface for multiple metrics
        - Time Series: Historical metrics tracking
    """

    def __init__(self, logger: logging.Logger, history_size: int = 100) -> None:
        """Initialize the performance monitor.

        Args:
            logger: Logger instance for monitoring events
            history_size: Maximum number of execution records to keep

        Preconditions:
            - logger must be a valid Logger instance
            - history_size must be > 0

        Side Effects:
            - Initializes metrics storage and history buffers
            - Logs monitor initialization
        """
        self._logger = logger
        self._history_size = history_size

        # Execution metrics
        self._execution_history: list[ExecutionMetrics] = []
        self._total_executions = 0
        self._successful_executions = 0
        self._failed_executions = 0
        self._total_execution_time_ms = 0.0

        # Cache metrics
        self._cache_metrics: dict[str, CacheMetrics] = {
            "FileIOCache": CacheMetrics(cache_name="FileIOCache"),
            "JSONCache": CacheMetrics(cache_name="JSONCache"),
            "PathCache": CacheMetrics(cache_name="PathCache"),
        }

        # System metrics
        self._peak_memory_mb = 0.0
        self._last_cleanup_time = time.time()
        self._cleanup_interval_seconds = 300  # 5 minutes

        self._logger.info("Initializing performance monitor")

    def record_execution(
        self, tool_name: str, duration_ms: float, status: str, error: str | None = None
    ) -> None:
        """Record a tool execution metric.

        Args:
            tool_name: Name of the tool executed
            duration_ms: Execution duration in milliseconds
            status: Execution status (success, failure, timeout)
            error: Error message if execution failed

        Side Effects:
            - Adds execution record to history
            - Updates execution statistics
            - Triggers cleanup if history exceeds size limit
        """
        now = time.time()
        metric = ExecutionMetrics(
            tool_name=tool_name,
            start_time=now - (duration_ms / 1000),
            end_time=now,
            duration_ms=duration_ms,
            status=status,
            error=error,
        )

        self._execution_history.append(metric)
        self._total_executions += 1
        self._total_execution_time_ms += duration_ms

        if metric.success:
            self._successful_executions += 1
        else:
            self._failed_executions += 1

        self._logger.debug(
            "Recorded execution: %s (%s, %.1fms)",
            tool_name,
            status,
            duration_ms,
        )

        # Prune history if needed
        if len(self._execution_history) > self._history_size:
            self._execution_history.pop(0)

    def record_cache_hit(self, cache_name: str) -> None:
        """Record a cache hit event.

        Args:
            cache_name: Name of the cache (FileIOCache, JSONCache, PathCache)
        """
        if cache_name in self._cache_metrics:
            self._cache_metrics[cache_name].hits += 1
            self._logger.debug(
                "Cache hit: %s",
                cache_name,
            )

    def record_cache_miss(self, cache_name: str) -> None:
        """Record a cache miss event.

        Args:
            cache_name: Name of the cache
        """
        if cache_name in self._cache_metrics:
            self._cache_metrics[cache_name].misses += 1
            self._logger.debug(
                "Cache miss: %s",
                cache_name,
            )

    def record_cache_eviction(self, cache_name: str, count: int = 1) -> None:
        """Record cache eviction event(s).

        Args:
            cache_name: Name of the cache
            count: Number of items evicted (default 1)
        """
        if cache_name in self._cache_metrics:
            self._cache_metrics[cache_name].evictions += count
            self._logger.debug(
                "Cache eviction: %s (%s items)",
                cache_name,
                count,
            )

    def update_cache_size(self, cache_name: str, size: int) -> None:
        """Update current cache size.

        Args:
            cache_name: Name of the cache
            size: Current number of entries in cache
        """
        if cache_name in self._cache_metrics:
            self._cache_metrics[cache_name].total_entries = size

    def get_metrics(self) -> PerformanceMetrics:
        """Get aggregated performance metrics.

        Returns:
            PerformanceMetrics with current performance statistics
        """
        avg_duration = (
            self._total_execution_time_ms / self._total_executions
            if self._total_executions > 0
            else 0.0
        )

        metrics = PerformanceMetrics(
            timestamp=datetime.now(),
            total_executions=self._total_executions,
            successful_executions=self._successful_executions,
            failed_executions=self._failed_executions,
            average_execution_time_ms=avg_duration,
            total_execution_time_ms=self._total_execution_time_ms,
            cache_metrics=dict(self._cache_metrics),
            peak_memory_mb=self._peak_memory_mb,
        )

        # Generate optimization suggestions
        metrics.optimization_suggestions = self._generate_suggestions(metrics)

        return metrics

    def _generate_suggestions(self, metrics: PerformanceMetrics) -> list[str]:
        """Generate optimization suggestions based on current metrics.

        Args:
            metrics: Current performance metrics

        Returns:
            List of actionable optimization suggestions
        """
        suggestions = []

        # Check execution performance
        if metrics.average_execution_time_ms > 1000:
            suggestions.append(
                f"🔍 平均実行時間が長いです ({metrics.average_execution_time_ms:.1f}ms). "
                "タイムアウト設定を確認してください。"
            )

        if metrics.success_rate < 90:
            suggestions.append(
                f"⚠️ 成功率が低いです ({metrics.success_rate:.1f}%). "
                "エラーログを確認して根本原因を特定してください。"
            )

        # Check cache performance
        for cache_name, cache_metrics in metrics.cache_metrics.items():
            if cache_metrics.hit_rate < 50 and cache_metrics.total_entries > 0:
                suggestions.append(
                    f"📊 {cache_name}のヒット率が低いです ({cache_metrics.hit_rate:.1f}%). "
                    "キャッシュサイズまたはTTLを調整してください。"
                )

            if cache_metrics.evictions > cache_metrics.total_entries * 10:
                suggestions.append(
                    f"🔄 {cache_name}の削除が多いです ({cache_metrics.evictions}件). "
                    "キャッシュサイズを増やしてください。"
                )

        return suggestions

    async def start_monitoring(self) -> None:
        """Start continuous performance monitoring (async).

        Runs in background to periodically check performance and
        generate optimization suggestions.

        Side Effects:
            - Runs indefinitely until cancelled
            - Logs performance status periodically
        """
        self._logger.info("Starting continuous performance monitoring")

        try:
            while True:
                await asyncio.sleep(60)  # Check every minute

                metrics = self.get_metrics()

                if metrics.optimization_suggestions:
                    self._logger.info(
                        "パフォーマンス提案: %s",
                        ", ".join(metrics.optimization_suggestions[:3]),
                    )

                # Periodic cleanup
                if time.time() - self._last_cleanup_time > self._cleanup_interval_seconds:
                    self._cleanup_old_metrics()
                    self._last_cleanup_time = time.time()

        except asyncio.CancelledError:
            self._logger.info("Performance monitoring stopped")

    def _cleanup_old_metrics(self) -> None:
        """Clean up old execution history records.

        Keeps memory usage bounded by removing old records beyond
        the history size limit.

        Side Effects:
            - Removes old execution records
            - Logs cleanup statistics
        """
        old_size = len(self._execution_history)

        # Keep only the most recent history_size records
        if old_size > self._history_size:
            self._execution_history = self._execution_history[-self._history_size :]
            self._logger.debug(
                "Cleaned up performance history: %s records removed",
                old_size - len(self._execution_history),
            )

    def get_summary(self) -> dict[str, Any]:
        """Get a performance summary in dict format.

        Returns:
            Dictionary with key performance indicators
        """
        metrics = self.get_metrics()

        return {
            "timestamp": metrics.timestamp.isoformat(),
            "total_executions": metrics.total_executions,
            "success_rate_percent": metrics.success_rate,
            "average_execution_time_ms": metrics.average_execution_time_ms,
            "cache_hit_rates": {
                name: cache.hit_rate
                for name, cache in metrics.cache_metrics.items()
            },
            "optimization_suggestions": metrics.optimization_suggestions,
        }
