from typing import Literal, Optional, List
from pydantic import BaseModel, ConfigDict, Field, field_validator

from src.core.utils.sanitize import NullByteError, sanitize_plain_text

# Server-side hard cap on an edited tribute message. Matches SEC-02 (INDL-44).
MAX_TRIBUTE_MESSAGE_LENGTH = 5000


class TimelineEventRequest(BaseModel):
    year: int = Field(..., ge=1700, le=2100)
    title: str = Field(..., max_length=255)
    description: Optional[str] = Field(None, max_length=500)


class CreateMemorialRequest(BaseModel):
    display_name: Optional[str] = None
    headstone_inscription: Optional[str] = None
    biography_text: Optional[str] = None
    video_url: Optional[str] = None
    visibility_config: Optional[dict] = None
    is_published: bool = False
    timeline_events: Optional[List[TimelineEventRequest]] = None


class UpdateMemorialRequest(BaseModel):
    display_name: Optional[str] = None
    headstone_inscription: Optional[str] = None
    biography_text: Optional[str] = None
    video_url: Optional[str] = None
    visibility_config: Optional[dict] = None
    is_published: Optional[bool] = None


class CreateTributeRequest(BaseModel):
    submitter_name: str
    submitter_email: Optional[str] = None
    relationship: Optional[str] = None
    message: str
    photo_url: Optional[str] = None


class UpdateTributeStatusRequest(BaseModel):
    # extra="forbid" blocks mass-assignment of read-only fields such as
    # tenant_id / memorial_id / submitter_email (INDL-44 SEC-04, OWASP API3).
    model_config = ConfigDict(extra="forbid")

    # Literal (not str) so "pending", "deleted", etc. are rejected with 422 and a
    # decided tribute can never be resurrected to the queue (INDL-44 SEC-05, A08).
    status: Literal["approved", "rejected"]

    # Optional edited message for the "Edit & approve" flow. When provided it is
    # sanitised to plain text (all HTML stripped) before persistence and must not
    # be blank (INDL-44 AC-11, SEC-02).
    message: Optional[str] = Field(None, max_length=MAX_TRIBUTE_MESSAGE_LENGTH)

    @field_validator("message")
    @classmethod
    def _sanitize_message(cls, value: Optional[str]) -> Optional[str]:
        if value is None:
            return None
        if not value.strip():
            raise ValueError("message must not be blank when provided")
        try:
            cleaned = sanitize_plain_text(value, max_length=MAX_TRIBUTE_MESSAGE_LENGTH)
        except NullByteError as exc:
            raise ValueError("message contains invalid control characters") from exc
        return cleaned


class ReplaceTimelineRequest(BaseModel):
    events: List[TimelineEventRequest]
