from datetime import date, datetime
from enum import Enum
from uuid import UUID

from pydantic import BaseModel, Field, field_validator


class ExamTypeEnum(str, Enum):
    weekly = "weekly"
    monthly = "monthly"
    final = "final"


class ReviewStatusEnum(str, Enum):
    careless = "careless"
    unknown = "unknown"
    nervous = "nervous"
    normal = "normal"


REVIEW_STATUS_VALUES = {s.value for s in ReviewStatusEnum}


class WrongQuestionStatusEnum(str, Enum):
    pending = "pending"
    ocr_done = "ocr_done"
    solved = "solved"
    failed = "failed"


class WrongQuestionCategoryEnum(str, Enum):
    regular = "regular"
    olympiad = "olympiad"


class AIProviderEnum(str, Enum):
    ollama = "ollama"
    openai = "openai"


class SchoolLevelEnum(str, Enum):
    junior_high = "junior_high"
    senior_high = "senior_high"


class TokenResponse(BaseModel):
    access_token: str
    refresh_token: str
    token_type: str = "bearer"


class UserRegister(BaseModel):
    username: str = Field(min_length=3, max_length=64)
    password: str = Field(min_length=6, max_length=128)


class UserLogin(BaseModel):
    username: str
    password: str


class RefreshRequest(BaseModel):
    refresh_token: str


class UserOut(BaseModel):
    id: UUID
    username: str
    is_superuser: bool = False
    created_at: datetime

    model_config = {"from_attributes": True}


class PublicSettingsOut(BaseModel):
    registration_enabled: bool


class AppFeaturesOut(BaseModel):
    ai_review_enabled: bool


class SystemSettingsOut(BaseModel):
    registration_enabled: bool
    ai_review_enabled: bool = True
    ai_provider: AIProviderEnum
    ollama_base_url: str | None = None
    ollama_model: str | None = None
    openai_base_url: str | None = None
    openai_model: str | None = None
    openai_api_key_set: bool = False
    ocr_service_url: str | None = None
    updated_at: datetime

    model_config = {"from_attributes": True}


class SystemSettingsUpdate(BaseModel):
    registration_enabled: bool | None = None
    ai_review_enabled: bool | None = None
    ai_provider: AIProviderEnum | None = None
    ollama_base_url: str | None = None
    ollama_model: str | None = None
    openai_base_url: str | None = None
    openai_model: str | None = None
    openai_api_key: str | None = None
    ocr_service_url: str | None = None


class AdminProfileUpdate(BaseModel):
    username: str | None = Field(default=None, min_length=3, max_length=64)
    current_password: str | None = None
    password: str | None = Field(default=None, min_length=6, max_length=128)


class AdminUserCreate(BaseModel):
    username: str = Field(min_length=3, max_length=64)
    password: str = Field(min_length=6, max_length=128)


class AdminUserPasswordUpdate(BaseModel):
    password: str = Field(min_length=6, max_length=128)


class AdminUserOut(BaseModel):
    id: UUID
    username: str
    is_superuser: bool
    created_at: datetime

    model_config = {"from_attributes": True}


class StudentCreate(BaseModel):
    name: str = Field(min_length=1, max_length=64)
    school_level: SchoolLevelEnum = SchoolLevelEnum.junior_high
    grade: str | None = None
    class_name: str | None = None
    school_name: str | None = Field(default=None, max_length=128)


class StudentUpdate(BaseModel):
    name: str | None = None
    school_level: SchoolLevelEnum | None = None
    grade: str | None = None
    class_name: str | None = None
    school_name: str | None = Field(default=None, max_length=128)


class StudentOut(BaseModel):
    id: UUID
    name: str
    school_level: SchoolLevelEnum
    grade: str | None
    class_name: str | None
    school_name: str | None
    has_avatar: bool = False
    created_at: datetime

    model_config = {"from_attributes": True}

    @classmethod
    def from_student(cls, student) -> "StudentOut":
        return cls(
            id=student.id,
            name=student.name,
            school_level=SchoolLevelEnum(student.school_level.value),
            grade=student.grade,
            class_name=student.class_name,
            school_name=student.school_name,
            has_avatar=bool(getattr(student, "avatar_path", None)),
            created_at=student.created_at,
        )


class SubjectOut(BaseModel):
    id: int
    name: str

    model_config = {"from_attributes": True}


class ScoreInput(BaseModel):
    subject_id: int
    total_score: float
    obtained_score: float
    review_statuses: list[ReviewStatusEnum] = []

    @field_validator("total_score")
    @classmethod
    def validate_total(cls, v: float) -> float:
        if v <= 0:
            raise ValueError("总分必须大于0")
        return v

    @field_validator("obtained_score")
    @classmethod
    def validate_obtained(cls, v: float, info) -> float:
        total = info.data.get("total_score")
        if total is not None and v > total:
            raise ValueError("得分不能大于总分")
        if v < 0:
            raise ValueError("得分不能为负")
        return v

    @field_validator("review_statuses")
    @classmethod
    def validate_review_statuses(cls, v: list[ReviewStatusEnum]) -> list[ReviewStatusEnum]:
        seen: set[str] = set()
        unique: list[ReviewStatusEnum] = []
        for item in v:
            key = item.value if isinstance(item, ReviewStatusEnum) else str(item)
            if key not in seen:
                seen.add(key)
                unique.append(item if isinstance(item, ReviewStatusEnum) else ReviewStatusEnum(key))
        return unique


class ScoreOut(BaseModel):
    id: UUID
    subject_id: int
    subject_name: str | None = None
    total_score: float
    obtained_score: float
    ratio: float
    review_statuses: list[ReviewStatusEnum] = []

    model_config = {"from_attributes": True}


class ExamCreate(BaseModel):
    exam_type: ExamTypeEnum
    exam_date: date
    title: str | None = None
    scores: list[ScoreInput] = []


class ExamUpdate(BaseModel):
    exam_type: ExamTypeEnum | None = None
    exam_date: date | None = None
    title: str | None = None
    scores: list[ScoreInput] | None = None


class ReviewScoreInput(BaseModel):
    subject_id: int
    review_statuses: list[ReviewStatusEnum] = []


class ExamReviewUpdate(BaseModel):
    reviews: list[ReviewScoreInput] = []


class ReviewInsightRequest(BaseModel):
    subject_name: str = Field(..., min_length=1, max_length=32)


class ReviewInsightResponse(BaseModel):
    insight: str


class ExamOut(BaseModel):
    id: UUID
    exam_type: ExamTypeEnum
    exam_date: date
    title: str | None
    created_at: datetime
    scores: list[ScoreOut] = []

    model_config = {"from_attributes": True}


class TrendPoint(BaseModel):
    exam_id: UUID
    exam_type: ExamTypeEnum
    exam_date: date
    title: str | None
    ratio: float
    ratio_percent: float
    delta: float | None = None
    delta_percent: float | None = None
    is_volatile: bool = False
    direction: str | None = None


class TrendResponse(BaseModel):
    subject_id: int
    subject_name: str
    threshold: float
    points: list[TrendPoint]


class WrongQuestionOut(BaseModel):
    id: UUID
    student_id: UUID
    subject_id: int
    subject_name: str | None = None
    category: WrongQuestionCategoryEnum
    image_path: str
    ocr_raw_text: str | None
    question_text: str | None
    solution_approach: str | None = None
    solution_text: str | None
    mark_regions: list[dict] | None = None
    has_annotated_image: bool = False
    has_cropped_image: bool = False
    error_message: str | None = None
    status: WrongQuestionStatusEnum
    created_at: datetime

    model_config = {"from_attributes": True}


class WrongQuestionUpdate(BaseModel):
    question_text: str | None = None
    solution_approach: str | None = None
    solution_text: str | None = None
    subject_id: int | None = None


class CompositionStatusEnum(str, Enum):
    pending = "pending"
    generating = "generating"
    done = "done"
    failed = "failed"


class CompositionInputModeEnum(str, Enum):
    manual = "manual"
    ocr = "ocr"


class CompositionCreate(BaseModel):
    topic: str = Field(..., min_length=1, max_length=4000)
    input_mode: CompositionInputModeEnum = CompositionInputModeEnum.manual


class CompositionOcrOut(BaseModel):
    text: str


class CompositionOut(BaseModel):
    id: UUID
    student_id: UUID
    topic: str
    input_mode: CompositionInputModeEnum
    writing_plan: str | None = None
    sample_essay: str | None = None
    error_message: str | None = None
    status: CompositionStatusEnum
    created_at: datetime
    updated_at: datetime

    model_config = {"from_attributes": True}


class BackupInfoOut(BaseModel):
    filename: str
    size_bytes: int
    created_at: datetime
