import datetime
from typing import Optional, Union

from pydantic import BaseModel, Extra, Field, StrictBool, StrictStr

from invokeai.app.models.image import ImageCategory, ResourceOrigin
from invokeai.app.util.misc import get_iso_timestamp


class ImageRecord(BaseModel):
    """Deserialized image record without metadata."""

    image_name: str = Field(description="The unique name of the image.")
    """The unique name of the image."""
    image_origin: ResourceOrigin = Field(description="The type of the image.")
    """The origin of the image."""
    image_category: ImageCategory = Field(description="The category of the image.")
    """The category of the image."""
    width: int = Field(description="The width of the image in px.")
    """The actual width of the image in px. This may be different from the width in metadata."""
    height: int = Field(description="The height of the image in px.")
    """The actual height of the image in px. This may be different from the height in metadata."""
    created_at: Union[datetime.datetime, str] = Field(
        description="The created timestamp of the image."
    )
    """The created timestamp of the image."""
    updated_at: Union[datetime.datetime, str] = Field(
        description="The updated timestamp of the image."
    )
    """The updated timestamp of the image."""
    deleted_at: Union[datetime.datetime, str, None] = Field(
        description="The deleted timestamp of the image."
    )
    """The deleted timestamp of the image."""
    is_intermediate: bool = Field(description="Whether this is an intermediate image.")
    """Whether this is an intermediate image."""
    session_id: Optional[str] = Field(
        default=None,
        description="The session ID that generated this image, if it is a generated image.",
    )
    """The session ID that generated this image, if it is a generated image."""
    node_id: Optional[str] = Field(
        default=None,
        description="The node ID that generated this image, if it is a generated image.",
    )
    """The node ID that generated this image, if it is a generated image."""


class ImageRecordChanges(BaseModel, extra=Extra.forbid):
    """A set of changes to apply to an image record.

    Only limited changes are valid:
      - `image_category`: change the category of an image
      - `session_id`: change the session associated with an image
      - `is_intermediate`: change the image's `is_intermediate` flag
    """

    image_category: Optional[ImageCategory] = Field(
        description="The image's new category."
    )
    """The image's new category."""
    session_id: Optional[StrictStr] = Field(
        default=None,
        description="The image's new session ID.",
    )
    """The image's new session ID."""
    is_intermediate: Optional[StrictBool] = Field(
        default=None, description="The image's new `is_intermediate` flag."
    )
    """The image's new `is_intermediate` flag."""


class ImageUrlsDTO(BaseModel):
    """The URLs for an image and its thumbnail."""

    image_name: str = Field(description="The unique name of the image.")
    """The unique name of the image."""
    image_url: str = Field(description="The URL of the image.")
    """The URL of the image."""
    thumbnail_url: str = Field(description="The URL of the image's thumbnail.")
    """The URL of the image's thumbnail."""


class ImageDTO(ImageRecord, ImageUrlsDTO):
    """Deserialized image record, enriched for the frontend."""

    board_id: Optional[str] = Field(
        description="The id of the board the image belongs to, if one exists."
    )
    """The id of the board the image belongs to, if one exists."""
    pass


def image_record_to_dto(
    image_record: ImageRecord, image_url: str, thumbnail_url: str, board_id: Optional[str]
) -> ImageDTO:
    """Converts an image record to an image DTO."""
    return ImageDTO(
        **image_record.dict(),
        image_url=image_url,
        thumbnail_url=thumbnail_url,
        board_id=board_id,
    )


def deserialize_image_record(image_dict: dict) -> ImageRecord:
    """Deserializes an image record."""

    # Retrieve all the values, setting "reasonable" defaults if they are not present.

    # TODO: do we really need to handle default values here? ideally the data is the correct shape...
    image_name = image_dict.get("image_name", "unknown")
    image_origin = ResourceOrigin(
        image_dict.get("image_origin", ResourceOrigin.INTERNAL.value)
    )
    image_category = ImageCategory(
        image_dict.get("image_category", ImageCategory.GENERAL.value)
    )
    width = image_dict.get("width", 0)
    height = image_dict.get("height", 0)
    session_id = image_dict.get("session_id", None)
    node_id = image_dict.get("node_id", None)
    created_at = image_dict.get("created_at", get_iso_timestamp())
    updated_at = image_dict.get("updated_at", get_iso_timestamp())
    deleted_at = image_dict.get("deleted_at", get_iso_timestamp())
    is_intermediate = image_dict.get("is_intermediate", False)

    return ImageRecord(
        image_name=image_name,
        image_origin=image_origin,
        image_category=image_category,
        width=width,
        height=height,
        session_id=session_id,
        node_id=node_id,
        created_at=created_at,
        updated_at=updated_at,
        deleted_at=deleted_at,
        is_intermediate=is_intermediate,
    )
