"""
VOC 게시글 데이터 모델

이 모듈은 VOC(Voice of Customer) 게시글의 데이터 구조를 정의합니다.
Pydantic을 사용하여 데이터 유효성을 입력 시점에 엄격하게 검증하며,
로직 내부에서는 오염된 데이터가 돌아다니지 않도록 합니다.

주요 기능:
    - 필수 필드 검증 (id, title, writer, department, status)
    - 선택 필드 처리 (content, attachment 등)
    - 날짜 형식 검증 (YYYY-MM-DD HH:MM:SS)
    - 공개 여부 검증 (0 또는 1만 허용)
    - 자동 타입 변환 (문자열 → 정수 등)

사용 예시:
    >>> post = VOCPost(
    ...     id="12345",
    ...     title="1호선 서면역 소음 발생",
    ...     writer="홍길동",
    ...     department="차량",
    ...     status="접수"
    ... )
    >>> print(post.id)
    12345

작성자: KH.Choi
최종 수정: 2026-02-17
버전: 2.0 (데이터 검증 강화)
"""
from pydantic import BaseModel, Field, field_validator, model_validator, ValidationInfo
from typing import Optional
from datetime import datetime
import re


class VOCPost(BaseModel):
    """
    VOC 게시글 데이터 모델
    
    목록 및 상세 정보를 포함하는 통합 모델입니다.
    Pydantic을 통해 데이터 유효성을 자동으로 검증하며,
    잘못된 데이터가 입력되면 ValidationError를 발생시킵니다.
    
    Attributes:
        id (str): 접수 번호 (Primary Key, 필수)
        title (str): 게시글 제목 (필수)
        writer (str): 작성자 이름 (필수)
        department (str): 담당 부서 (필수, 예: "차량", "시설")
        status (str): 처리 상태 (필수, 예: "접수", "처리중", "완료")
        date (str): 작성일 (YYYY-MM-DD HH:MM:SS 형식)
        is_public (int): 공개 여부 (0: 비공개, 1: 공개)
        is_related (int): 관심글 여부 (0: 일반, 1: 관심)
        channel (str): 접수 경로 (예: "인터넷", "전화")
        content (str): 질의 내용 본문 (선택)
        attachment (str): 첨부파일명 (선택)
    
    Validators:
        validate_id: ID가 비어있지 않은지 확인
        validate_is_public: 0 또는 1만 허용
        validate_is_related: 0 또는 1만 허용
        validate_date_format: 날짜 형식 검증 (선택)
    
    사용 시나리오:
        1. 크롤링 결과 → VOCPost 변환 (데이터 검증)
        2. DB 조회 결과 → VOCPost 변환
        3. UI 입력 → VOCPost 변환
    """
    
    # ========================================================================
    # 필수 필드 (Required Fields)
    # ========================================================================
    
    id: str = Field(
        ..., 
        description="접수 번호 (Primary Key)",
        min_length=1,
        max_length=50
    )
    
    title: str = Field(
        ..., 
        description="게시글 제목",
        min_length=1,
        max_length=500
    )
    
    writer: str = Field(
        ..., 
        description="작성자 이름",
        min_length=1,
        max_length=100
    )
    
    department: str = Field(
        default="",
        description="담당 부서 (예: 차량, 시설, 역무)",
        max_length=50
    )
    
    status: str = Field(
        ..., 
        description="처리 상태 (예: 접수, 처리중, 답변완료)",
        min_length=1,
        max_length=50
    )
    
    # ========================================================================
    # 기본값이 있는 필드 (Fields with Defaults)
    # ========================================================================
    
    date: str = Field(
        "", 
        description="작성 일자 (YYYY-MM-DD HH:MM:SS 형식, 상세 수집 시 갱신)"
    )
    
    is_public: int = Field(
        1, 
        description="공개 여부 (1: 공개, 0: 비공개)",
        ge=0,  # 0 이상
        le=1   # 1 이하
    )
    
    is_related: int = Field(
        0, 
        description="관심 부서/키워드 연관 여부 (1: 연관, 0: 미연관)",
        ge=0,
        le=1
    )
    
    channel: str = Field(
        "", 
        description="접수 채널 (예: 홈페이지, 모바일, 전화)"
    )
    
    # ========================================================================
    # 선택 필드 (Optional Fields)
    # ========================================================================
    
    content: Optional[str] = Field(
        None, 
        description="질의 내용 본문 (초기엔 None, 상세 조회 후 채워짐)"
    )
    
    answer: Optional[str] = Field(
        None, 
        description="답변 내용"
    )
    
    station: Optional[str] = Field(
        None, 
        description="관련 역명 (예: 서면, 부산역)"
    )
    
    attachment: Optional[str] = Field(
        None, 
        description="첨부파일명 (없으면 None 또는 '없음')"
    )
    
    voc_type: Optional[str] = Field(
        None, 
        description="VOC 유형 분류 (예: 불만, 제안, 문의)"
    )
    
    response_type: Optional[str] = Field(
        None, 
        description="응답 방식 (예: 전화, 메일, 문자)"
    )
    
    summary: Optional[str] = Field(
        None, 
        description="내용 요약 (AI 분석용, 향후 확장)"
    )
    
    # ========================================================================
    # 타임스탬프 필드 (Timestamp Fields)
    # ========================================================================
    
    created_at: Optional[datetime] = Field(
        None, 
        description="데이터 생성 시각 (DB 자동 생성)"
    )
    
    updated_at: Optional[datetime] = Field(
        None, 
        description="데이터 수정 시각"
    )
    
    last_checked_at: Optional[datetime] = Field(
        None, 
        description="마지막 크롤링 확인 시각"
    )
    
    checked_at: Optional[datetime] = Field(
        None, 
        description="사용자가 게시글을 확인한 시각 (읽음 처리)"
    )
    
    # ========================================================================
    # Validators (데이터 유효성 검사)
    # ========================================================================
    
    @field_validator('id')
    @classmethod
    def validate_id(cls, v):
        """
        ID 검증: 비어있지 않고, 공백만 있지 않은지 확인
        
        Args:
            v: 검증할 ID 값
            
        Returns:
            str: 검증된 ID (앞뒤 공백 제거)
            
        Raises:
            ValueError: ID가 비어있거나 공백만 있는 경우
        """
        if not v or not v.strip():
            raise ValueError("ID는 비어있을 수 없습니다.")
        return v.strip()
    
    @field_validator('title', 'writer', 'status')
    @classmethod
    def validate_required_strings(cls, v, info: ValidationInfo):
        """
        필수 문자열 필드 검증: 비어있지 않은지 확인

        department는 비어있을 수 있으므로 제외합니다.

        Args:
            v: 검증할 값
            info: 검증 정보

        Returns:
            str: 검증된 값 (앞뒤 공백 제거)

        Raises:
            ValueError: 값이 비어있거나 공백만 있는 경우
        """
        if not v or not v.strip():
            raise ValueError(f"{info.field_name}은(는) 비어있을 수 없습니다.")
        return v.strip()

    @field_validator('department')
    @classmethod
    def validate_department(cls, v):
        """
        부서 필드 검증: 비어있을 수 있으며, 공백 제거만 수행

        웹사이트에서 부서 정보가 비어있는 경우가 있어
        빈 문자열을 허용합니다.

        Args:
            v: 검증할 값

        Returns:
            str: 공백이 제거된 값 (빈 문자열 가능)
        """
        if v is None:
            return ""
        return v.strip()

    @field_validator('is_public', 'is_related')
    @classmethod
    def validate_binary_int(cls, v, info: ValidationInfo):
        """
        이진 정수 필드 검증: 0 또는 1만 허용

        크롤링 데이터에서 문자열 "1"이나 "0"이 들어올 수 있어
        정수로 변환 후 검증합니다.

        Args:
            v: 검증할 값
            info: 검증 정보

        Returns:
            int: 검증된 값 (0 또는 1)

        Raises:
            ValueError: 0 또는 1이 아닌 경우
        """
        # 문자열인 경우 정수로 변환
        if isinstance(v, str):
            if v not in ("0", "1"):
                raise ValueError(f"{info.field_name}은(는) 0 또는 1만 가능합니다. (입력값: {v})")
            return int(v)

        if v not in (0, 1):
            raise ValueError(f"{info.field_name}은(는) 0 또는 1만 가능합니다. (입력값: {v})")
        return v
    
    @field_validator('date')
    @classmethod
    def validate_date_format(cls, v):
        """
        날짜 형식 검증: YYYY-MM-DD HH:MM:SS 또는 YYYY-MM-DD HH:MM 형식 확인

        빈 문자열은 허용합니다 (초기 상태).
        HH:MM 형식(초 없음)이 들어오면 HH:MM:00으로 변환합니다.

        Args:
            v: 검증할 날짜 문자열

        Returns:
            str: 검증된 날짜 문자열 (YYYY-MM-DD HH:MM:SS 형식)

        Raises:
            ValueError: 날짜 형식이 잘못된 경우
        """
        if not v:  # 빈 문자열은 허용
            return v

        # YYYY-MM-DD HH:MM:SS 형식 검증
        pattern_full = r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$'
        # YYYY-MM-DD HH:MM 형식 검증 (초 없음)
        pattern_short = r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$'

        if re.match(pattern_full, v):
            # 완전한 형식 - 그대로 검증
            try:
                datetime.strptime(v, "%Y-%m-%d %H:%M:%S")
                return v
            except ValueError as e:
                raise ValueError(f"유효하지 않은 날짜입니다: {v} ({e})")

        elif re.match(pattern_short, v):
            # 초 없는 형식 - :00 추가
            v_full = f"{v}:00"
            try:
                datetime.strptime(v_full, "%Y-%m-%d %H:%M:%S")
                return v_full
            except ValueError as e:
                raise ValueError(f"유효하지 않은 날짜입니다: {v} ({e})")

        else:
            raise ValueError(
                f"날짜 형식이 잘못되었습니다. "
                f"'YYYY-MM-DD HH:MM:SS' 또는 'YYYY-MM-DD HH:MM' 형식이어야 합니다. (입력값: {v})"
            )
    
    @model_validator(mode='before')
    @classmethod
    def validate_attachment(cls, values):
        """
        첨부파일 필드 정규화: "없음" → None 변환

        크롤링 결과에서 "없음"이 들어오는 경우가 있어,
        이를 None으로 통일합니다.

        Args:
            values: 전체 필드 값 딕셔너리

        Returns:
            dict: 정규화된 필드 값
        """
        if isinstance(values, dict):
            attachment = values.get('attachment')
            if attachment and isinstance(attachment, str) and attachment.strip() in ("없음", "N/A", "-"):
                values['attachment'] = None
        return values
    
    # ========================================================================
    # Config (설정)
    # ========================================================================

    # Pydantic v2 모델 설정
    model_config = {
        # datetime을 ISO 형식 문자열로 변환
        "json_encoders": {
            datetime: lambda v: v.isoformat() if v else None
        },

        # 추가 필드 허용 안 함 (엄격 모드)
        "extra": 'forbid',

        # 필드 별칭 허용 (v2: populate_by_name)
        "populate_by_name": True,

        # 스키마 예시 (v2: json_schema_extra)
        "json_schema_extra": {
            "example": {
                "id": "12345",
                "title": "1호선 서면역 소음 발생",
                "writer": "홍길동",
                "department": "차량",
                "status": "접수",
                "date": "2026-02-17 14:30:00",
                "is_public": 1,
                "is_related": 1,
                "channel": "인터넷",
                "content": "서면역에서 열차 소음이 심합니다.",
                "attachment": None
            }
        }
    }
