import uuid
from datetime import datetime
from typing import List, Optional

from pydantic import UUID4, BaseModel, EmailStr, StrictStr, validator
from sqlmodel import Column, DateTime, Field, Relationship, SQLModel

BIO_CHAR_LIMIT = 256


class TimestampsMixin(BaseModel):
    created_at: Optional[datetime] = Field(
        sa_column=Column(
            DateTime,
            default=datetime.utcnow,
            nullable=False,
        )
    )
    updated_at: Optional[datetime] = Field(
        sa_column=Column(
            DateTime,
            default=datetime.utcnow,
            onupdate=datetime.utcnow,
            nullable=False,
        )
    )


class UserBase(SQLModel):
    email: EmailStr = Field(index=True, nullable=False)
    name: StrictStr
    bio: Optional[StrictStr]

    @validator("bio", pre=True, always=True)
    def bio_length(cls: BaseModel, v: str) -> str:
        def valid_bio_len(bio: str) -> bool:
            return len(bio) <= BIO_CHAR_LIMIT

        if v is not None and not valid_bio_len(v):
            raise ValueError("Bio is too long.")
        return v


class UserBaseWithPasswordHash(UserBase):
    password_hash: Optional[StrictStr]


class UserCreate(UserBaseWithPasswordHash):
    pass

    class Config:
        schema_extra = {
            "example": {
                "name": "Firstname Lastname",
                "email": "<EMAIL>",
                "password_hash": "<PASSWORD>_",
                "bio": "logy #puns",
            }
        }


class UserUpdate(UserBase):
    pass

    class Config:
        schema_extra = {
            "example": {
                "name": "NewFirstname NewLastname",
                "email": "<EMAIL>",
                "bio": "metrics #puns",
            }
        }


class User(UserBaseWithPasswordHash, TimestampsMixin, table=True):
    __tablename__ = "users"
    id: UUID4 = Field(default_factory=uuid.uuid4, primary_key=True, nullable=False)
    items: List["Item"] = Relationship(
        back_populates="user",
    )


class UserRead(UserBase):
    id: UUID4
    created_at: datetime
    updated_at: datetime
    items: List["Item"]


class ItemBase(SQLModel):
    name: StrictStr
    description: Optional[StrictStr]


class ItemCreate(ItemBase):
    user_id: UUID4

    class Config:
        schema_extra = {
            "example": {
                "name": "A New Item",
                "description": "Many copious details.",
                "user_id": "<UUID4>",
            }
        }


class ItemUpdate(ItemBase):
    pass

    class Config:
        schema_extra = {
            "example": {
                "name": "A New New Item",
                "description": "This item has been updated.",
            }
        }


class Item(ItemBase, TimestampsMixin, table=True):
    __tablename__ = "items"
    id: UUID4 = Field(default_factory=uuid.uuid4, primary_key=True, nullable=False)
    user_id: UUID4 = Field(default=None, foreign_key="users.id", nullable=False)
    user: User = Relationship(
        back_populates="items",
    )


class ItemRead(ItemBase):
    id: UUID4
    user_id: UUID4
    created_at: datetime
    updated_at: datetime
    user: User


UserRead.update_forward_refs()

sqlmodel.Column pydantic.validator sqlmodel.Relationship sqlmodel.Field
[((708, 741), 'sqlmodel.Field', 'Field', ([], {'index': '(True)', 'nullable': '(False)'}), '(index=True, nullable=False)\n', (713, 741), False, 'from sqlmodel import Column, DateTime, Field, Relationship, SQLModel\n'), ((797, 836), 'pydantic.validator', 'validator', (['"""bio"""'], {'pre': '(True)', 'always': '(True)'}), "('bio', pre=True, always=True)\n", (806, 836), False, 'from pydantic import UUID4, BaseModel, EmailStr, StrictStr, validator\n'), ((1868, 1935), 'sqlmodel.Field', 'Field', ([], {'default_factory': 'uuid.uuid4', 'primary_key': '(True)', 'nullable': '(False)'}), '(default_factory=uuid.uuid4, primary_key=True, nullable=False)\n', (1873, 1935), False, 'from sqlmodel import Column, DateTime, Field, Relationship, SQLModel\n'), ((1962, 1997), 'sqlmodel.Relationship', 'Relationship', ([], {'back_populates': '"""user"""'}), "(back_populates='user')\n", (1974, 1997), False, 'from sqlmodel import Column, DateTime, Field, Relationship, SQLModel\n'), ((2821, 2888), 'sqlmodel.Field', 'Field', ([], {'default_factory': 'uuid.uuid4', 'primary_key': '(True)', 'nullable': '(False)'}), '(default_factory=uuid.uuid4, primary_key=True, nullable=False)\n', (2826, 2888), False, 'from sqlmodel import Column, DateTime, Field, Relationship, SQLModel\n'), ((2910, 2969), 'sqlmodel.Field', 'Field', ([], {'default': 'None', 'foreign_key': '"""users.id"""', 'nullable': '(False)'}), "(default=None, foreign_key='users.id', nullable=False)\n", (2915, 2969), False, 'from sqlmodel import Column, DateTime, Field, Relationship, SQLModel\n'), ((2987, 3023), 'sqlmodel.Relationship', 'Relationship', ([], {'back_populates': '"""items"""'}), "(back_populates='items')\n", (2999, 3023), False, 'from sqlmodel import Column, DateTime, Field, Relationship, SQLModel\n'), ((336, 393), 'sqlmodel.Column', 'Column', (['DateTime'], {'default': 'datetime.utcnow', 'nullable': '(False)'}), '(DateTime, default=datetime.utcnow, nullable=False)\n', (342, 393), False, 'from sqlmodel import Column, DateTime, Field, Relationship, SQLModel\n'), ((509, 596), 'sqlmodel.Column', 'Column', (['DateTime'], {'default': 'datetime.utcnow', 'onupdate': 'datetime.utcnow', 'nullable': '(False)'}), '(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow,\n    nullable=False)\n', (515, 596), False, 'from sqlmodel import Column, DateTime, Field, Relationship, SQLModel\n')]