from datetime import datetime
from typing import Any, Dict, List, Optional, Union

from pydantic import BaseModel, Field, field_validator

from .base import BaseResponse, StatusResponse


class DeckUser(BaseModel):
    primaryKey: str
    uid: str
    displayname: str


class DeckPermissions(BaseModel):
    PERMISSION_READ: bool
    PERMISSION_EDIT: bool
    PERMISSION_MANAGE: bool
    PERMISSION_SHARE: bool


class DeckLabel(BaseModel):
    id: int
    title: str
    color: str
    boardId: Optional[int] = None
    cardId: Optional[int] = None


class DeckACL(BaseModel):
    id: int
    participant: DeckUser
    type: int
    boardId: int
    permissionEdit: bool
    permissionShare: bool
    permissionManage: bool
    owner: bool


class DeckBoardSettings(BaseModel):
    calendar: bool
    cardDetailsInModal: Optional[bool] = Field(default=None, alias="cardDetailsInModal")
    cardIdBadge: Optional[bool] = Field(default=None, alias="cardIdBadge")
    groupLimit: Optional[List[Dict[str, str]]] = Field(default=None, alias="groupLimit")
    notify_due: Optional[str] = Field(default=None, alias="notify-due")


class DeckBoard(BaseModel):
    id: int
    title: str
    owner: DeckUser
    color: str
    archived: bool
    labels: List[DeckLabel]
    acl: List[DeckACL]
    permissions: DeckPermissions
    users: List[DeckUser]
    deletedAt: int
    lastModified: Optional[int] = None
    settings: Optional[DeckBoardSettings] = None
    etag: Optional[str] = Field(default=None, alias="ETag")

    @field_validator("settings", mode="before")
    @classmethod
    def validate_settings(cls, v):
        # Handle case where API returns empty array instead of dict/null
        if isinstance(v, list) and len(v) == 0:
            return None
        return v


class DeckAssignedUser(BaseModel):
    id: int
    participant: DeckUser
    cardId: int
    type: int


class DeckCard(BaseModel):
    id: int
    title: str
    stackId: int
    type: str
    order: int
    archived: bool
    owner: Union[str, DeckUser]  # Can be either string or user object
    description: Optional[str] = None
    duedate: Optional[datetime] = None
    done: Optional[datetime] = None
    lastModified: Optional[int] = None
    createdAt: Optional[int] = None
    labels: Optional[List[DeckLabel]] = None
    assignedUsers: Optional[List[Union[DeckUser, DeckAssignedUser]]] = None
    attachments: Optional[List[Any]] = None  # Define a proper Attachment model later
    attachmentCount: Optional[int] = None
    deletedAt: Optional[int] = None
    commentsUnread: Optional[int] = None
    overdue: Optional[int] = None
    etag: Optional[str] = Field(default=None, alias="ETag")

    @field_validator("owner", mode="before")
    @classmethod
    def validate_owner(cls, v):
        # Handle case where API returns user object instead of string
        if isinstance(v, dict):
            return v.get("uid", v.get("primaryKey", str(v)))
        return v

    @field_validator("assignedUsers", mode="before")
    @classmethod
    def validate_assigned_users(cls, v):
        # Handle different formats of assigned users from the API
        if not v:
            return v

        validated_users = []
        for user in v:
            if isinstance(user, dict):
                # Check if it's an assignment object with participant
                if "participant" in user:
                    validated_users.append(user)
                # Check if it's a direct user object
                elif "uid" in user or "primaryKey" in user:
                    validated_users.append(user)
            else:
                validated_users.append(user)

        return validated_users


class DeckCardSummary(BaseModel):
    """Compact projection of a :class:`DeckCard` for list/overview views.

    Drops the heavy fields that dominate token cost when many cards are
    returned at once — the full ``description`` (kept only as a short
    ``descriptionPreview``), the nested ``labels``/``assignedUsers``/
    ``attachments`` objects (kept as flat title/uid lists + counts), and the
    ``etag``/``order``/``*Modified``/``createdAt``/``deletedAt``/``overdue``/
    ``type``/``owner`` bookkeeping fields. Fetch a single card with
    ``deck_get_card`` (or ``detail="full"``) when the full body is needed.
    """

    id: int
    title: str
    stackId: int
    archived: bool = False
    duedate: datetime | None = None
    done: datetime | None = None
    labels: list[str] = Field(
        default_factory=list, description="Label titles assigned to the card"
    )
    assignedUsers: list[str] = Field(
        default_factory=list, description="UIDs of users assigned to the card"
    )
    attachmentCount: int | None = Field(
        default=None, description="Number of attachments on the card"
    )
    commentsUnread: int | None = Field(
        default=None, description="Number of unread comments (Deck exposes no total)"
    )
    hasDescription: bool = Field(
        default=False, description="Whether the card has a non-empty description"
    )
    descriptionPreview: str | None = Field(
        default=None, description="Truncated preview of the card description"
    )


class DeckStack(BaseModel):
    id: int
    title: str
    boardId: int
    order: int
    deletedAt: int
    lastModified: Optional[int] = None
    # Cards may be projected to DeckCardSummary when a tool is called with
    # detail="summary" (the default for list tools).
    cards: list[DeckCard | DeckCardSummary] | None = None
    etag: Optional[str] = Field(default=None, alias="ETag")


class DeckAttachmentExtendedData(BaseModel):
    filesize: int
    mimetype: str
    info: Dict[str, str]
    # Populated for type="file" (Files share) attachments via FilesAppService.
    path: str | None = None
    fileid: int | None = None
    hasPreview: bool | None = None
    permissions: int | None = None


class DeckAttachment(BaseModel):
    id: int
    cardId: int
    type: str
    data: str
    lastModified: int
    createdAt: int
    createdBy: str
    deletedAt: int
    extendedData: DeckAttachmentExtendedData


class DeckComment(BaseModel):
    id: int
    objectId: int
    message: str
    actorId: str
    actorType: str
    actorDisplayName: str
    creationDateTime: datetime
    mentions: List[Dict[str, str]]
    replyTo: Optional[Any] = None  # Self-referencing, handle later if needed


class DeckCommentSummary(BaseModel):
    """Compact projection of a :class:`DeckComment` for list views.

    Drops ``mentions``, ``actorType``, ``actorDisplayName`` and ``replyTo``,
    keeping only the fields needed to read the conversation. Long messages are
    truncated by the tool layer via ``message_max_length``.
    """

    id: int
    actorId: str
    message: str
    creationDateTime: datetime


class DeckSession(BaseModel):
    token: str


class DeckConfig(BaseModel):
    calendar: bool
    cardDetailsInModal: bool
    cardIdBadge: bool
    groupLimit: Optional[List[Dict[str, str]]] = None


# Response Models for MCP Tools


class ListBoardsResponse(BaseResponse):
    """Response model for listing deck boards."""

    boards: List[DeckBoard] = Field(description="List of deck boards")
    total: int = Field(description="Total number of boards")


class CreateBoardResponse(BaseResponse):
    """Response model for board creation."""

    id: int = Field(description="The created board ID")
    title: str = Field(description="The created board title")
    color: str = Field(description="The created board color")


class BoardOperationResponse(StatusResponse):
    """Response model for board operations like update/delete."""

    board_id: int = Field(description="ID of the affected board")


# Stack Response Models


class ListStacksResponse(BaseResponse):
    """Response model for listing deck stacks."""

    stacks: List[DeckStack] = Field(description="List of deck stacks")
    total: int = Field(description="Total number of stacks")


class StackOverview(BaseModel):
    """A stack plus its compact card rows, for the board-overview tool."""

    id: int = Field(description="Stack ID")
    title: str = Field(description="Stack title")
    order: int | None = Field(default=None, description="Stack sort order")
    card_count: int = Field(description="Number of cards returned for this stack")
    cards: list[DeckCardSummary] = Field(
        default_factory=list, description="Compact card rows in this stack"
    )


class BoardOverviewResponse(BaseResponse):
    """Compact whole-board snapshot: board → stacks → summary card rows.

    A single-call replacement for deck_get_board + deck_get_stacks that keeps
    the response small enough to fit the token budget on large boards.
    """

    board_id: int = Field(description="Board ID")
    title: str = Field(description="Board title")
    labels: list[str] = Field(
        default_factory=list, description="Board label titles (legend)"
    )
    stacks: list[StackOverview] = Field(
        default_factory=list, description="Stacks with compact card rows"
    )
    total_cards: int = Field(description="Total cards across all returned stacks")


class CreateStackResponse(BaseResponse):
    """Response model for stack creation."""

    id: int = Field(description="The created stack ID")
    title: str = Field(description="The created stack title")
    order: int = Field(description="The created stack order")


class StackOperationResponse(StatusResponse):
    """Response model for stack operations like update/delete."""

    stack_id: int = Field(description="ID of the affected stack")
    board_id: int = Field(description="ID of the board containing the stack")


# Card Response Models


class CreateCardResponse(BaseResponse):
    """Response model for card creation."""

    id: int = Field(description="The created card ID")
    title: str = Field(description="The created card title")
    description: Optional[str] = Field(description="The created card description")
    stackId: int = Field(description="The stack ID the card belongs to")


class CardOperationResponse(StatusResponse):
    """Response model for card operations like update/delete."""

    card_id: int = Field(description="ID of the affected card")
    stack_id: int = Field(description="ID of the stack containing the card")
    board_id: int = Field(description="ID of the board containing the card")
    labels: list[str] | None = Field(
        default=None,
        description=(
            "Label titles on the card after the operation, when relevant — "
            "e.g. after a cross-board move that remaps board-scoped labels to "
            "the destination board"
        ),
    )


# Label Response Models


class CreateLabelResponse(BaseResponse):
    """Response model for label creation."""

    id: int = Field(description="The created label ID")
    title: str = Field(description="The created label title")
    color: str = Field(description="The created label color")


class ListCardsResponse(BaseResponse):
    """Response model for listing deck cards."""

    cards: list[DeckCard | DeckCardSummary] = Field(
        description="List of deck cards (summaries unless detail='full')"
    )
    total: int = Field(description="Total number of cards")


class ListLabelsResponse(BaseResponse):
    """Response model for listing deck labels."""

    labels: list[DeckLabel] = Field(description="List of deck labels")
    total: int = Field(description="Total number of labels")


class LabelOperationResponse(StatusResponse):
    """Response model for label operations like update/delete."""

    label_id: int = Field(description="ID of the affected label")
    board_id: int = Field(description="ID of the board containing the label")


# Comment Response Models


class ListCardCommentsResponse(BaseResponse):
    """Response model for listing card comments."""

    results: list[DeckComment | DeckCommentSummary] = Field(
        description="Card comments in this page (summaries unless detail='full')"
    )
    count: int = Field(
        description=(
            "Number of comments returned in this page (page size, not the "
            "server-side total — the Deck list endpoint does not expose a total)."
        )
    )


class CardCommentResponse(BaseResponse):
    """Response model returned when a single card comment is created or updated."""

    comment: DeckComment = Field(description="The created or updated card comment")


class CardCommentOperationResponse(StatusResponse):
    """Response model for card comment operations that don't return comment data (e.g. delete)."""

    card_id: int = Field(description="ID of the card the comment belongs to")
    comment_id: int = Field(description="ID of the affected comment")


# Attachment Response Models


class AttachFileResponse(BaseResponse):
    """Response model for attaching an existing Nextcloud file to a Deck card.

    The attachment is created by sharing the file with the card via the standard
    OCS Sharing API using ``shareType=12`` (``IShare::TYPE_DECK``). The returned
    ``attachment_id`` is the share ID, which is also the Deck attachment ID.
    """

    attachment_id: int = Field(
        description="ID of the created attachment (share ID)",
    )
    card_id: int = Field(description="ID of the card the file is attached to")
    path: str = Field(description="Path of the shared file in the user's Files")


class ListAttachmentsResponse(BaseResponse):
    """Response model for listing card attachments."""

    results: list[DeckAttachment] = Field(
        description="Attachments on the card (both type='file' and type='deck_file')"
    )
    count: int = Field(description="Number of attachments returned")


class AttachmentOperationResponse(StatusResponse):
    """Response model for attachment operations that don't return data (e.g. delete)."""

    card_id: int = Field(description="ID of the card the attachment belongs to")
    attachment_id: int = Field(description="ID of the affected attachment")
