"""
프로젝트 전역 예외 정의

모든 커스텀 예외는 이 파일에 정의되어야 하며,
AI 에이전트는 임의로 Exception을 던지지 않고 반드시 여기 정의된 예외를 사용해야 합니다.

예외 명명 규칙: [Domain][Action]Error
예시: DataParsingError, ReportGenerationError

작성자: KH.Choi
최종 수정: 2026-02-17
버전: 1.0
"""


# ============================================================================
# 기본 예외 클래스 (Base Exceptions)
# ============================================================================

class VOCMonitorError(Exception):
    """
    모든 커스텀 예외의 기본 클래스
    
    Attributes:
        message (str): 사용자에게 표시할 한국어 메시지
        code (str): 에러 코드 (로깅 및 추적용)
        details (dict): 추가 컨텍스트 정보
    """
    def __init__(self, message: str, code: str = "UNKNOWN", details: dict = None):
        self.message = message
        self.code = code
        self.details = details or {}
        super().__init__(self.message)
    
    def __str__(self):
        return f"[{self.code}] {self.message}"


# ============================================================================
# 데이터 관련 예외 (Data Layer)
# ============================================================================

class DataError(VOCMonitorError):
    """데이터 처리 관련 기본 예외"""
    def __init__(self, message: str, details: dict = None):
        super().__init__(message, "DATA_ERROR", details)


class DataValidationError(DataError):
    """데이터 검증 실패"""
    def __init__(self, message: str, field: str = None):
        details = {"field": field} if field else {}
        super().__init__(message, details)
        self.code = "DATA_VALIDATION_ERROR"


class DatabaseError(DataError):
    """데이터베이스 작업 실패"""
    def __init__(self, message: str, query: str = None):
        details = {"query": query} if query else {}
        super().__init__(message, details)
        self.code = "DATABASE_ERROR"


# ============================================================================
# 크롤링 관련 예외 (Scraper Layer)
# ============================================================================

class ScraperError(VOCMonitorError):
    """크롤링 관련 기본 예외"""
    def __init__(self, message: str, details: dict = None):
        super().__init__(message, "SCRAPER_ERROR", details)


class LoginFailedError(ScraperError):
    """로그인 실패"""
    def __init__(self, message: str = "로그인에 실패했습니다."):
        super().__init__(message)
        self.code = "LOGIN_FAILED"


class SessionExpiredError(ScraperError):
    """세션 만료"""
    def __init__(self, message: str = "세션이 만료되었습니다. 재로그인이 필요합니다."):
        super().__init__(message)
        self.code = "SESSION_EXPIRED"


class PageLoadError(ScraperError):
    """페이지 로드 실패"""
    def __init__(self, message: str, url: str = None):
        details = {"url": url} if url else {}
        super().__init__(message, details)
        self.code = "PAGE_LOAD_ERROR"


# ============================================================================
# 보고서 생성 관련 예외 (Report Layer)
# ============================================================================

class ReportError(VOCMonitorError):
    """보고서 생성 관련 기본 예외"""
    def __init__(self, message: str, details: dict = None):
        super().__init__(message, "REPORT_ERROR", details)


class ReportGenerationError(ReportError):
    """보고서 생성 실패"""
    def __init__(self, message: str, report_type: str = None):
        details = {"report_type": report_type} if report_type else {}
        super().__init__(message, details)
        self.code = "REPORT_GENERATION_ERROR"


class TemplateNotFoundError(ReportError):
    """템플릿 파일을 찾을 수 없음"""
    def __init__(self, template_path: str):
        message = f"템플릿 파일을 찾을 수 없습니다: {template_path}"
        super().__init__(message, {"template_path": template_path})
        self.code = "TEMPLATE_NOT_FOUND"


class TrainInfoNotFoundError(ReportError):
    """
    열차 정보 조회 실패 (부분 실패 허용)
    
    이 예외는 보고서 생성을 중단하지 않고,
    해당 필드를 비워두거나 기본값을 사용하도록 처리됩니다.
    """
    def __init__(self, message: str = "열차 정보를 조회할 수 없습니다.", train_number: str = None):
        details = {"train_number": train_number} if train_number else {}
        super().__init__(message, details)
        self.code = "TRAIN_INFO_NOT_FOUND"
        self.is_partial_failure = True  # 부분 실패 플래그


class TimetableServiceError(ReportError):
    """시각표 서비스 오류"""
    def __init__(self, message: str = "시각표 조회 중 오류가 발생했습니다."):
        super().__init__(message)
        self.code = "TIMETABLE_SERVICE_ERROR"
        self.is_partial_failure = True


# ============================================================================
# 시스템 관련 예외 (System Layer)
# ============================================================================

class SystemError(VOCMonitorError):
    """시스템 관련 기본 예외"""
    def __init__(self, message: str, details: dict = None):
        super().__init__(message, "SYSTEM_ERROR", details)


class ConfigurationError(SystemError):
    """설정 파일 오류"""
    def __init__(self, message: str, config_file: str = None):
        details = {"config_file": config_file} if config_file else {}
        super().__init__(message, details)
        self.code = "CONFIGURATION_ERROR"


class DependencyError(SystemError):
    """필수 의존성 누락"""
    def __init__(self, dependency: str):
        message = f"필수 모듈이 설치되지 않았습니다: {dependency}"
        super().__init__(message, {"dependency": dependency})
        self.code = "DEPENDENCY_ERROR"


# ============================================================================
# UI 관련 예외 (View Layer)
# ============================================================================

class UIError(VOCMonitorError):
    """UI 관련 기본 예외"""
    def __init__(self, message: str, details: dict = None):
        super().__init__(message, "UI_ERROR", details)


class DialogCancelledError(UIError):
    """사용자가 다이얼로그를 취소함"""
    def __init__(self, message: str = "사용자가 작업을 취소했습니다."):
        super().__init__(message)
        self.code = "DIALOG_CANCELLED"
        self.is_user_action = True  # 사용자 의도적 취소


# ============================================================================
# 유틸리티 함수
# ============================================================================

def is_partial_failure(error: Exception) -> bool:
    """
    부분 실패 여부 확인
    
    부분 실패는 전체 작업을 중단하지 않고,
    해당 부분만 기본값으로 처리하거나 건너뛸 수 있는 에러입니다.
    
    Args:
        error: 확인할 예외 객체
        
    Returns:
        bool: 부분 실패 여부
    """
    return hasattr(error, 'is_partial_failure') and error.is_partial_failure


def is_user_action(error: Exception) -> bool:
    """
    사용자 의도적 행동 여부 확인
    
    사용자가 의도적으로 취소한 경우 에러 로그를 남기지 않습니다.
    
    Args:
        error: 확인할 예외 객체
        
    Returns:
        bool: 사용자 의도적 행동 여부
    """
    return hasattr(error, 'is_user_action') and error.is_user_action
