import logging
import traceback
import numpy as np
from typing import Any, Dict, Optional, List, Callable
from functools import wraps

logger = logging.getLogger(__name__)

class EvaluationExceptionHandler:
    """평가 시스템 전용 예외 처리기"""
    
    @staticmethod
    def handle_metric_calculation_error(metric_name: str, default_value: float = 0.0):
        """메트릭 계산 오류 처리 데코레이터"""
        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                try:
                    return func(*args, **kwargs)
                except ZeroDivisionError as e:
                    logger.error(f"Zero division error in {metric_name}: {str(e)}")
                    return default_value
                except ValueError as e:
                    logger.error(f"Value error in {metric_name}: {str(e)}")
                    return default_value
                except KeyError as e:
                    logger.error(f"Key error in {metric_name}: {str(e)}")
                    return default_value
                except MemoryError as e:
                    logger.error(f"Memory error in {metric_name}: {str(e)}")
                    return default_value
                except Exception as e:
                    logger.error(f"Unexpected error in {metric_name}: {str(e)}")
                    logger.debug(traceback.format_exc())
                    return default_value
            return wrapper
        return decorator
    
    @staticmethod
    def validate_metric_result(metric_name: str, result: Any, expected_range: tuple) -> bool:
        """메트릭 결과 검증"""
        if not isinstance(result, (int, float)):
            logger.warning(f"{metric_name}: Invalid result type {type(result)}")
            return False
        
        # 무한대 값 검증
        if np.isinf(result):
            logger.warning(f"{metric_name}: Result is infinite: {result}")
            return False
        
        # NaN 값 검증
        if np.isnan(result):
            logger.warning(f"{metric_name}: Result is NaN: {result}")
            return False
        
        if not (expected_range[0] <= result <= expected_range[1]):
            logger.warning(f"{metric_name}: Result {result} out of expected range {expected_range}")
            return False
        
        return True
    
    @staticmethod
    def handle_vertices_key_error(mesh_data: Dict, fallback_vertices: List[List[float]] = None) -> Dict:
        """vertices 키 누락 오류 처리"""
        if 'vertices' not in mesh_data:
            logger.warning("vertices 키가 누락되었습니다. 대체 데이터 생성 중...")
            
            if fallback_vertices is None:
                # 기본 정육면체 vertices 생성
                fallback_vertices = [
                    [0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0],  # 하단
                    [0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1]   # 상단
                ]
            
            mesh_data['vertices'] = fallback_vertices
            
            # faces도 생성
            if 'faces' not in mesh_data:
                mesh_data['faces'] = [
                    [0, 1, 2], [0, 2, 3],  # 하단
                    [4, 7, 6], [4, 6, 5],  # 상단
                    [0, 4, 5], [0, 5, 1],  # 앞면
                    [2, 6, 7], [2, 7, 3],  # 뒷면
                    [0, 3, 7], [0, 7, 4],  # 왼쪽
                    [1, 5, 6], [1, 6, 2]   # 오른쪽
                ]
        
        return mesh_data
    
    @staticmethod
    def handle_infinite_values(value: float, metric_name: str, max_value: float = 1000.0) -> float:
        """무한대 값 처리"""
        if np.isinf(value):
            if value > 0:  # 양의 무한대
                logger.warning(f"{metric_name}: 양의 무한대 값 감지, {max_value}로 대체")
                return max_value
            else:  # 음의 무한대
                logger.warning(f"{metric_name}: 음의 무한대 값 감지, 0으로 대체")
                return 0.0
        
        if np.isnan(value):
            logger.warning(f"{metric_name}: NaN 값 감지, 0으로 대체")
            return 0.0
        
        return value
    
    @staticmethod
    def safe_divide(numerator: float, denominator: float, default_value: float = 0.0) -> float:
        """안전한 나눗셈"""
        if denominator == 0 or np.isclose(denominator, 0):
            logger.warning(f"0으로 나누기 시도: {numerator} / {denominator}")
            return default_value
        
        result = numerator / denominator
        
        # 무한대 값 검사
        if np.isinf(result):
            logger.warning(f"나눗셈 결과가 무한대: {numerator} / {denominator}")
            return default_value
        
        return result
    
    @staticmethod
    def validate_mesh_data(mesh_data: Dict) -> bool:
        """메시 데이터 검증"""
        required_keys = ['vertices', 'faces']
        
        for key in required_keys:
            if key not in mesh_data:
                logger.error(f"메시 데이터에 필수 키 '{key}'가 누락되었습니다")
                return False
            
            if not mesh_data[key]:
                logger.error(f"메시 데이터의 '{key}'가 비어있습니다")
                return False
        
        # vertices 검증
        vertices = mesh_data['vertices']
        if not isinstance(vertices, (list, np.ndarray)):
            logger.error("vertices는 리스트 또는 numpy 배열이어야 합니다")
            return False
        
        if len(vertices) == 0:
            logger.error("vertices가 비어있습니다")
            return False
        
        # faces 검증
        faces = mesh_data['faces']
        if not isinstance(faces, (list, np.ndarray)):
            logger.error("faces는 리스트 또는 numpy 배열이어야 합니다")
            return False
        
        if len(faces) == 0:
            logger.error("faces가 비어있습니다")
            return False
        
        return True
    
    @staticmethod
    def create_robust_error_handler(metric_name: str, default_value: float = 0.0) -> Callable:
        """견고한 오류 처리기 생성"""
        def error_handler(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                try:
                    result = func(*args, **kwargs)
                    
                    # 결과 검증
                    if result is None:
                        logger.warning(f"{metric_name}: 결과가 None입니다")
                        return default_value
                    
                    if isinstance(result, float):
                        if np.isinf(result):
                            logger.warning(f"{metric_name}: 결과가 무한대입니다")
                            return default_value
                        
                        if np.isnan(result):
                            logger.warning(f"{metric_name}: 결과가 NaN입니다")
                            return default_value
                    
                    return result
                    
                except KeyError as e:
                    logger.error(f"{metric_name}: 키 오류 - {str(e)}")
                    return default_value
                except ValueError as e:
                    logger.error(f"{metric_name}: 값 오류 - {str(e)}")
                    return default_value
                except MemoryError as e:
                    logger.error(f"{metric_name}: 메모리 오류 - {str(e)}")
                    return default_value
                except Exception as e:
                    logger.error(f"{metric_name}: 예상치 못한 오류 - {str(e)}")
                    logger.debug(traceback.format_exc())
                    return default_value
            
            return wrapper
        return error_handler
