"""
상세한 로깅 시스템
각 지표 계산 과정의 상세 로깅과 중간 결과 저장을 담당합니다.
"""

import logging
import json
import os
import time
import psutil
import traceback
from datetime import datetime
from typing import Dict, Any, Optional, List
from contextlib import contextmanager


class DetailedLogger:
    """상세한 로깅을 담당하는 클래스"""
    
    def __init__(self, log_dir: str = "logs", enable_performance_monitoring: bool = True):
        """
        상세 로거 초기화
        
        Args:
            log_dir (str): 로그 파일 저장 디렉토리
            enable_performance_monitoring (bool): 성능 모니터링 활성화 여부
        """
        self.log_dir = log_dir
        self.enable_performance_monitoring = enable_performance_monitoring
        
        # 로그 디렉토리 생성
        os.makedirs(log_dir, exist_ok=True)
        
        # 세션 ID 설정 (로거 설정 전에)
        self.current_session_id = datetime.now().strftime("%Y%m%d_%H%M%S")
        
        # 중간 결과 저장용
        self.intermediate_results = []
        
        # 로거 설정
        self._setup_loggers()
        
        # 성능 모니터링
        if self.enable_performance_monitoring:
            self.performance_monitor = PerformanceMonitor()
        else:
            self.performance_monitor = None
    
    def _setup_loggers(self):
        """로거들을 설정합니다."""
        # 메인 로거
        self.main_logger = logging.getLogger('evaluation_main')
        self.main_logger.setLevel(logging.INFO)
        
        # 메트릭별 로거
        self.metric_loggers = {}
        for metric in ['2d_map', '3d_map', 'chamfer_distance', 'emd', 'class_accuracy']:
            logger = logging.getLogger(f'evaluation_{metric}')
            logger.setLevel(logging.DEBUG)
            self.metric_loggers[metric] = logger
        
        # 오류 로거
        self.error_logger = logging.getLogger('evaluation_error')
        self.error_logger.setLevel(logging.ERROR)
        
        # 성능 로거
        self.performance_logger = logging.getLogger('evaluation_performance')
        self.performance_logger.setLevel(logging.INFO)
        
        # 핸들러 설정
        self._setup_handlers()
    
    def _setup_handlers(self):
        """로그 핸들러들을 설정합니다."""
        # 메인 로그 파일 핸들러
        main_handler = logging.FileHandler(
            os.path.join(self.log_dir, f'evaluation_main_{self.current_session_id}.log'),
            encoding='utf-8'
        )
        main_handler.setFormatter(logging.Formatter(
            '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        ))
        self.main_logger.addHandler(main_handler)
        
        # 메트릭별 로그 파일 핸들러
        for metric, logger in self.metric_loggers.items():
            handler = logging.FileHandler(
                os.path.join(self.log_dir, f'evaluation_{metric}_{self.current_session_id}.log'),
                encoding='utf-8'
            )
            handler.setFormatter(logging.Formatter(
                '%(asctime)s - %(levelname)s - %(message)s'
            ))
            logger.addHandler(handler)
        
        # 오류 로그 파일 핸들러
        error_handler = logging.FileHandler(
            os.path.join(self.log_dir, f'evaluation_error_{self.current_session_id}.log'),
            encoding='utf-8'
        )
        error_handler.setFormatter(logging.Formatter(
            '%(asctime)s - %(levelname)s - %(message)s\n%(pathname)s:%(lineno)d\n%(funcName)s\n'
        ))
        self.error_logger.addHandler(error_handler)
        
        # 성능 로그 파일 핸들러
        if self.enable_performance_monitoring:
            perf_handler = logging.FileHandler(
                os.path.join(self.log_dir, f'evaluation_performance_{self.current_session_id}.log'),
                encoding='utf-8'
            )
            perf_handler.setFormatter(logging.Formatter(
                '%(asctime)s - %(levelname)s - %(message)s'
            ))
            self.performance_logger.addHandler(perf_handler)
        
        # 콘솔 핸들러 (모든 로거에 추가)
        console_handler = logging.StreamHandler()
        console_handler.setFormatter(logging.Formatter(
            '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        ))
        
        for logger in [self.main_logger, self.error_logger, self.performance_logger]:
            logger.addHandler(console_handler)
        
        for logger in self.metric_loggers.values():
            logger.addHandler(console_handler)
    
    def log_metric_start(self, metric_name: str, **kwargs):
        """메트릭 계산 시작을 로깅합니다."""
        logger = self.metric_loggers.get(metric_name, self.main_logger)
        logger.info(f"=== {metric_name.upper()} 계산 시작 ===")
        
        # 입력 파라미터 로깅
        for key, value in kwargs.items():
            if isinstance(value, (dict, list)):
                logger.debug(f"입력 파라미터 {key}: {type(value).__name__} (크기: {len(value)})")
            else:
                logger.debug(f"입력 파라미터 {key}: {value}")
        
        # 성능 모니터링 시작
        if self.performance_monitor:
            self.performance_monitor.start_metric(metric_name)
    
    def log_metric_step(self, metric_name: str, step: str, details: Dict[str, Any] = None):
        """메트릭 계산 단계를 로깅합니다."""
        logger = self.metric_loggers.get(metric_name, self.main_logger)
        logger.info(f"[{metric_name}] {step}")
        
        if details:
            for key, value in details.items():
                if isinstance(value, (dict, list)):
                    logger.debug(f"  {key}: {type(value).__name__} (크기: {len(value)})")
                else:
                    logger.debug(f"  {key}: {value}")
    
    def log_metric_result(self, metric_name: str, result: Any, execution_time: float = None):
        """메트릭 계산 결과를 로깅합니다."""
        logger = self.metric_loggers.get(metric_name, self.main_logger)
        
        logger.info(f"=== {metric_name.upper()} 계산 완료 ===")
        logger.info(f"결과: {result}")
        
        if execution_time:
            logger.info(f"실행 시간: {execution_time:.2f}초")
        
        # 성능 모니터링 종료
        if self.performance_monitor:
            self.performance_monitor.end_metric(metric_name, execution_time)
        
        # 중간 결과 저장
        self._save_intermediate_result(metric_name, result, execution_time)
    
    def log_metric_error(self, metric_name: str, error: Exception, context: Dict[str, Any] = None):
        """메트릭 계산 오류를 로깅합니다."""
        logger = self.metric_loggers.get(metric_name, self.error_logger)
        
        logger.error(f"=== {metric_name.upper()} 계산 오류 ===")
        logger.error(f"오류 타입: {type(error).__name__}")
        logger.error(f"오류 메시지: {str(error)}")
        logger.error(f"스택 트레이스:\n{traceback.format_exc()}")
        
        if context:
            logger.error("오류 발생 컨텍스트:")
            for key, value in context.items():
                logger.error(f"  {key}: {value}")
        
        # 성능 모니터링 종료 (오류 시)
        if self.performance_monitor:
            self.performance_monitor.end_metric(metric_name, None, error=True)
    
    def log_system_info(self):
        """시스템 정보를 로깅합니다."""
        self.main_logger.info("=== 시스템 정보 ===")
        self.main_logger.info(f"CPU 개수: {psutil.cpu_count()}")
        self.main_logger.info(f"메모리 총량: {psutil.virtual_memory().total / (1024**3):.2f} GB")
        self.main_logger.info(f"사용 가능 메모리: {psutil.virtual_memory().available / (1024**3):.2f} GB")
        self.main_logger.info(f"디스크 사용량: {psutil.disk_usage('/').percent:.1f}%")
    
    def log_evaluation_start(self, model_path: str, reference_path: str):
        """평가 시작을 로깅합니다."""
        self.main_logger.info("=" * 60)
        self.main_logger.info("3D 객체인식 평가 시작")
        self.main_logger.info("=" * 60)
        self.main_logger.info(f"모델 파일: {model_path}")
        self.main_logger.info(f"참조 파일: {reference_path}")
        self.main_logger.info(f"세션 ID: {self.current_session_id}")
        
        # 시스템 정보 로깅
        self.log_system_info()
    
    def log_evaluation_complete(self, results: Dict[str, Any]):
        """평가 완료를 로깅합니다."""
        self.main_logger.info("=" * 60)
        self.main_logger.info("3D 객체인식 평가 완료")
        self.main_logger.info("=" * 60)
        
        if 'comprehensive_score' in results:
            self.main_logger.info(f"종합 점수: {results['comprehensive_score']:.2f}")
        
        if 'grade' in results:
            self.main_logger.info(f"성능 등급: {results['grade']}")
        
        # 개별 지표 결과 로깅
        if 'metrics' in results:
            self.main_logger.info("개별 지표 결과:")
            for metric_name, value in results['metrics'].items():
                self.main_logger.info(f"  {metric_name}: {value}")
        
        # 성능 요약 로깅
        if self.performance_monitor:
            self.performance_monitor.log_summary(self.performance_logger)
    
    def _save_intermediate_result(self, metric_name: str, result: Any, execution_time: float = None):
        """중간 결과를 저장합니다."""
        intermediate_result = {
            'timestamp': datetime.now().isoformat(),
            'metric_name': metric_name,
            'result': result,
            'execution_time': execution_time,
            'session_id': self.current_session_id
        }
        
        self.intermediate_results.append(intermediate_result)
        
        # JSONL 파일에 저장
        intermediate_file = os.path.join(
            self.log_dir, 
            f"intermediate_results_{self.current_session_id}.jsonl"
        )
        
        with open(intermediate_file, 'a', encoding='utf-8') as f:
            f.write(json.dumps(intermediate_result, ensure_ascii=False) + '\n')
    
    @contextmanager
    def metric_context(self, metric_name: str, **kwargs):
        """메트릭 계산을 위한 컨텍스트 매니저"""
        start_time = time.time()
        
        try:
            self.log_metric_start(metric_name, **kwargs)
            yield
            execution_time = time.time() - start_time
            self.log_metric_result(metric_name, "계산 완료", execution_time)
        except Exception as e:
            execution_time = time.time() - start_time
            self.log_metric_error(metric_name, e, {'execution_time': execution_time})
            raise


class PerformanceMonitor:
    """성능 모니터링을 담당하는 클래스"""
    
    def __init__(self):
        """성능 모니터 초기화"""
        self.metric_times = {}
        self.metric_memory = {}
        self.metric_cpu = {}
        self.current_metric = None
        self.start_time = None
        self.start_memory = None
        self.start_cpu = None
    
    def start_metric(self, metric_name: str):
        """메트릭 계산 시작 시점을 기록합니다."""
        self.current_metric = metric_name
        self.start_time = time.time()
        self.start_memory = psutil.virtual_memory().used
        self.start_cpu = psutil.cpu_percent()
    
    def end_metric(self, metric_name: str, execution_time: float = None, error: bool = False):
        """메트릭 계산 종료 시점을 기록합니다."""
        if self.current_metric != metric_name:
            return
        
        if execution_time is None:
            execution_time = time.time() - self.start_time
        
        end_memory = psutil.virtual_memory().used
        end_cpu = psutil.cpu_percent()
        
        # 메트릭별 성능 데이터 저장
        self.metric_times[metric_name] = execution_time
        self.metric_memory[metric_name] = {
            'start': self.start_memory,
            'end': end_memory,
            'peak': end_memory - self.start_memory
        }
        self.metric_cpu[metric_name] = {
            'start': self.start_cpu,
            'end': end_cpu,
            'average': (self.start_cpu + end_cpu) / 2
        }
        
        # 상태 초기화
        self.current_metric = None
        self.start_time = None
        self.start_memory = None
        self.start_cpu = None
    
    def log_summary(self, logger: logging.Logger):
        """성능 요약을 로깅합니다."""
        logger.info("=== 성능 요약 ===")
        
        total_time = sum(self.metric_times.values())
        logger.info(f"총 실행 시간: {total_time:.2f}초")
        
        logger.info("메트릭별 실행 시간:")
        for metric, time_taken in self.metric_times.items():
            percentage = (time_taken / total_time) * 100 if total_time > 0 else 0
            logger.info(f"  {metric}: {time_taken:.2f}초 ({percentage:.1f}%)")
        
        logger.info("메트릭별 메모리 사용량:")
        for metric, memory_data in self.metric_memory.items():
            peak_mb = memory_data['peak'] / (1024**2)
            logger.info(f"  {metric}: {peak_mb:.2f} MB")
        
        logger.info("메트릭별 CPU 사용률:")
        for metric, cpu_data in self.metric_cpu.items():
            logger.info(f"  {metric}: 평균 {cpu_data['average']:.1f}%")


# 전역 로거 인스턴스
_global_logger = None

def get_logger(log_dir: str = "logs", enable_performance_monitoring: bool = True) -> DetailedLogger:
    """전역 로거 인스턴스를 반환합니다."""
    global _global_logger
    if _global_logger is None:
        _global_logger = DetailedLogger(log_dir, enable_performance_monitoring)
    return _global_logger

def reset_logger():
    """전역 로거를 리셋합니다."""
    global _global_logger
    _global_logger = None
