"""
성능 모니터링 유틸리티
메모리 사용량, CPU 사용률, 실행 시간 등을 모니터링합니다.
"""

import time
import psutil
import threading
import logging
from typing import Dict, List, Optional, Callable
from contextlib import contextmanager
from dataclasses import dataclass
from datetime import datetime


@dataclass
class PerformanceMetrics:
    """성능 메트릭 데이터 클래스"""
    timestamp: datetime
    cpu_percent: float
    memory_used_mb: float
    memory_percent: float
    disk_io_read_mb: float
    disk_io_write_mb: float
    network_io_sent_mb: float
    network_io_recv_mb: float


class PerformanceMonitor:
    """성능 모니터링을 담당하는 클래스"""
    
    def __init__(self, monitoring_interval: float = 1.0, log_threshold_mb: float = 100.0):
        """
        성능 모니터 초기화
        
        Args:
            monitoring_interval (float): 모니터링 간격 (초)
            log_threshold_mb (float): 로그 임계값 (MB)
        """
        self.monitoring_interval = monitoring_interval
        self.log_threshold_mb = log_threshold_mb
        self.is_monitoring = False
        self.monitoring_thread = None
        self.metrics_history: List[PerformanceMetrics] = []
        self.callbacks: List[Callable[[PerformanceMetrics], None]] = []
        
        # 초기 시스템 상태
        self.initial_disk_io = psutil.disk_io_counters()
        self.initial_network_io = psutil.net_io_counters()
        
        # 로거 설정
        self.logger = logging.getLogger('performance_monitor')
    
    def start_monitoring(self):
        """성능 모니터링을 시작합니다."""
        if self.is_monitoring:
            return
        
        self.is_monitoring = True
        self.monitoring_thread = threading.Thread(target=self._monitoring_loop, daemon=True)
        self.monitoring_thread.start()
        
        self.logger.info("성능 모니터링이 시작되었습니다.")
    
    def stop_monitoring(self):
        """성능 모니터링을 중지합니다."""
        if not self.is_monitoring:
            return
        
        self.is_monitoring = False
        if self.monitoring_thread:
            self.monitoring_thread.join(timeout=2.0)
        
        self.logger.info("성능 모니터링이 중지되었습니다.")
    
    def _monitoring_loop(self):
        """모니터링 루프"""
        while self.is_monitoring:
            try:
                metrics = self._collect_metrics()
                self.metrics_history.append(metrics)
                
                # 콜백 실행
                for callback in self.callbacks:
                    try:
                        callback(metrics)
                    except Exception as e:
                        self.logger.error(f"콜백 실행 중 오류: {e}")
                
                # 메모리 사용량이 임계값을 초과하면 경고
                if metrics.memory_used_mb > self.log_threshold_mb:
                    self.logger.warning(
                        f"높은 메모리 사용량 감지: {metrics.memory_used_mb:.2f} MB "
                        f"({metrics.memory_percent:.1f}%)"
                    )
                
                time.sleep(self.monitoring_interval)
                
            except Exception as e:
                self.logger.error(f"모니터링 중 오류: {e}")
                time.sleep(self.monitoring_interval)
    
    def _collect_metrics(self) -> PerformanceMetrics:
        """현재 시스템 메트릭을 수집합니다."""
        # CPU 사용률
        cpu_percent = psutil.cpu_percent()
        
        # 메모리 사용량
        memory = psutil.virtual_memory()
        memory_used_mb = memory.used / (1024**2)
        memory_percent = memory.percent
        
        # 디스크 I/O
        disk_io = psutil.disk_io_counters()
        disk_io_read_mb = (disk_io.read_bytes - self.initial_disk_io.read_bytes) / (1024**2)
        disk_io_write_mb = (disk_io.write_bytes - self.initial_disk_io.write_bytes) / (1024**2)
        
        # 네트워크 I/O
        network_io = psutil.net_io_counters()
        network_io_sent_mb = (network_io.bytes_sent - self.initial_network_io.bytes_sent) / (1024**2)
        network_io_recv_mb = (network_io.bytes_recv - self.initial_network_io.bytes_recv) / (1024**2)
        
        return PerformanceMetrics(
            timestamp=datetime.now(),
            cpu_percent=cpu_percent,
            memory_used_mb=memory_used_mb,
            memory_percent=memory_percent,
            disk_io_read_mb=disk_io_read_mb,
            disk_io_write_mb=disk_io_write_mb,
            network_io_sent_mb=network_io_sent_mb,
            network_io_recv_mb=network_io_recv_mb
        )
    
    def add_callback(self, callback: Callable[[PerformanceMetrics], None]):
        """성능 메트릭 콜백을 추가합니다."""
        self.callbacks.append(callback)
    
    def remove_callback(self, callback: Callable[[PerformanceMetrics], None]):
        """성능 메트릭 콜백을 제거합니다."""
        if callback in self.callbacks:
            self.callbacks.remove(callback)
    
    def get_current_metrics(self) -> Optional[PerformanceMetrics]:
        """현재 메트릭을 반환합니다."""
        if not self.metrics_history:
            return None
        return self.metrics_history[-1]
    
    def get_metrics_summary(self) -> Dict:
        """메트릭 요약을 반환합니다."""
        if not self.metrics_history:
            return {}
        
        cpu_values = [m.cpu_percent for m in self.metrics_history]
        memory_values = [m.memory_used_mb for m in self.metrics_history]
        
        return {
            'total_samples': len(self.metrics_history),
            'monitoring_duration': (
                self.metrics_history[-1].timestamp - self.metrics_history[0].timestamp
            ).total_seconds(),
            'cpu': {
                'min': min(cpu_values),
                'max': max(cpu_values),
                'avg': sum(cpu_values) / len(cpu_values)
            },
            'memory': {
                'min_mb': min(memory_values),
                'max_mb': max(memory_values),
                'avg_mb': sum(memory_values) / len(memory_values)
            },
            'peak_memory_mb': max(memory_values),
            'total_disk_read_mb': self.metrics_history[-1].disk_io_read_mb,
            'total_disk_write_mb': self.metrics_history[-1].disk_io_write_mb,
            'total_network_sent_mb': self.metrics_history[-1].network_io_sent_mb,
            'total_network_recv_mb': self.metrics_history[-1].network_io_recv_mb
        }
    
    def clear_history(self):
        """메트릭 히스토리를 초기화합니다."""
        self.metrics_history.clear()
        self.logger.info("성능 메트릭 히스토리가 초기화되었습니다.")


class MemoryProfiler:
    """메모리 프로파일링을 담당하는 클래스"""
    
    def __init__(self):
        """메모리 프로파일러 초기화"""
        self.snapshots: List[Dict] = []
        self.logger = logging.getLogger('memory_profiler')
    
    def take_snapshot(self, label: str = None):
        """메모리 스냅샷을 생성합니다."""
        process = psutil.Process()
        memory_info = process.memory_info()
        memory_percent = process.memory_percent()
        
        snapshot = {
            'timestamp': datetime.now(),
            'label': label,
            'rss_mb': memory_info.rss / (1024**2),  # 실제 메모리 사용량
            'vms_mb': memory_info.vms / (1024**2),  # 가상 메모리 사용량
            'percent': memory_percent,
            'num_threads': process.num_threads(),
            'num_fds': process.num_fds() if hasattr(process, 'num_fds') else 0
        }
        
        self.snapshots.append(snapshot)
        
        if label:
            self.logger.info(f"메모리 스냅샷 [{label}]: {snapshot['rss_mb']:.2f} MB")
        
        return snapshot
    
    def get_memory_diff(self, start_label: str, end_label: str) -> Dict:
        """두 스냅샷 간의 메모리 차이를 계산합니다."""
        start_snapshot = None
        end_snapshot = None
        
        for snapshot in self.snapshots:
            if snapshot['label'] == start_label:
                start_snapshot = snapshot
            elif snapshot['label'] == end_label:
                end_snapshot = snapshot
        
        if not start_snapshot or not end_snapshot:
            return {}
        
        return {
            'start_label': start_label,
            'end_label': end_label,
            'rss_diff_mb': end_snapshot['rss_mb'] - start_snapshot['rss_mb'],
            'vms_diff_mb': end_snapshot['vms_mb'] - start_snapshot['vms_mb'],
            'percent_diff': end_snapshot['percent'] - start_snapshot['percent'],
            'time_diff_seconds': (
                end_snapshot['timestamp'] - start_snapshot['timestamp']
            ).total_seconds()
        }
    
    def clear_snapshots(self):
        """스냅샷을 초기화합니다."""
        self.snapshots.clear()
        self.logger.info("메모리 스냅샷이 초기화되었습니다.")


@contextmanager
def monitor_performance(monitor: PerformanceMonitor, label: str = None):
    """성능 모니터링 컨텍스트 매니저"""
    if label:
        monitor.take_snapshot(f"{label}_start")
    
    monitor.start_monitoring()
    
    try:
        yield monitor
    finally:
        monitor.stop_monitoring()
        
        if label:
            monitor.take_snapshot(f"{label}_end")


@contextmanager
def memory_profiling(profiler: MemoryProfiler, label: str = None):
    """메모리 프로파일링 컨텍스트 매니저"""
    start_snapshot = profiler.take_snapshot(f"{label}_start" if label else "start")
    
    try:
        yield profiler
    finally:
        end_snapshot = profiler.take_snapshot(f"{label}_end" if label else "end")
        
        if label:
            diff = profiler.get_memory_diff(f"{label}_start", f"{label}_end")
            if diff:
                profiler.logger.info(
                    f"메모리 프로파일링 [{label}]: "
                    f"RSS 차이: {diff['rss_diff_mb']:.2f} MB, "
                    f"실행 시간: {diff['time_diff_seconds']:.2f}초"
                )


# 전역 인스턴스
_global_performance_monitor = None
_global_memory_profiler = None

def get_performance_monitor() -> PerformanceMonitor:
    """전역 성능 모니터 인스턴스를 반환합니다."""
    global _global_performance_monitor
    if _global_performance_monitor is None:
        _global_performance_monitor = PerformanceMonitor()
    return _global_performance_monitor

def get_memory_profiler() -> MemoryProfiler:
    """전역 메모리 프로파일러 인스턴스를 반환합니다."""
    global _global_memory_profiler
    if _global_memory_profiler is None:
        _global_memory_profiler = MemoryProfiler()
    return _global_memory_profiler
