from app.core.database import Base
from sqlalchemy import (
    Boolean,
    DateTime,
    Enum,
    ForeignKey,
    Index,
    Integer,
    PrimaryKeyConstraint,
    String,
    func,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from starlette.authentication import BaseUser


class User(Base, BaseUser):
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(
        Integer, primary_key=True, nullable=False, autoincrement=True
    )
    username: Mapped[str] = mapped_column(
        String(255), unique=True, nullable=False, index=True
    )
    email: Mapped[str] = mapped_column(
        String(255), unique=True, nullable=False, index=True
    )
    hashed_password: Mapped[str] = mapped_column(String(60), nullable=False)
    # age_level 컬럼을 Enum으로 정의
    age_level_choices = ["10", "20", "30", "40", "50", "60", "70", "80"]
    age_level: Mapped[str] = mapped_column(
        Enum(*age_level_choices, name="age_level_enum"),
        nullable=False,
        default="10",
    )
    is_active: Mapped[bool] = mapped_column(Boolean, default=True)
    created_at: Mapped[DateTime] = mapped_column(DateTime, server_default=func.now())

    # One-to-many relationship with Post (DynamicMapped + lazy="dynamic")
    posts_user: Mapped[list["Post"]] = relationship("Post", back_populates="user_posts")

    # # Many-to-many relationship with Group
    # user_groups: DynamicMapped["UserGroupAssociation"] = relationship(
    #     "UserGroupAssociation", back_populates="user", lazy="dynamic"
    # )
    # n:m 관계 (Group) – 최적의 lazy 옵션: selectin
    group_user: Mapped[list["Group"]] = relationship(
        "Group",
        secondary="user_group_association",
        back_populates="user_group",
        lazy="selectin",
    )

    def __repr__(self) -> str:
        return f"id={self.id}, username={self.username}"

    @property
    def is_authenticated(self) -> bool:
        return self.is_active

    @property
    def display_name(self) -> str:
        return self.username

    @property
    def identity(self) -> str:
        return self.username


# 1:N Relationship - Posts
class Post(Base):
    __tablename__ = "posts"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    user_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False
    )
    title: Mapped[str] = mapped_column(String(255), nullable=False)
    content: Mapped[str] = mapped_column(String(10000), nullable=False)
    is_published: Mapped[bool] = mapped_column(Boolean, default=False)
    created_at: Mapped[DateTime] = mapped_column(DateTime, server_default=func.now())
    updated_at: Mapped[DateTime] = mapped_column(
        DateTime, server_default=func.now(), onupdate=func.now()
    )
    # tags: Mapped[dict] = mapped_column(MutableDict.as_mutable(JSONB), default=[]) // sqlite 지원 안함
    view_count: Mapped[int] = mapped_column(Integer, default=0)

    # Many-to-one relationship with User (using dynamic loading)
    user_posts: Mapped["User"] = relationship("User", back_populates="posts_user")

    def __repr__(self) -> str:
        return f"Post(id={self.id}, user_id={self.user_id}, title={self.title})"

    __table_args__ = (
        Index("idx_posts_user_id", "user_id"),
        Index("idx_posts_created_at", "created_at"),
        Index(
            "idx_posts_user_id_created_at", "user_id", "created_at"
        ),  # Composite index
    )


# N:M Relationship - Users and Groups
# Association table for many-to-many relationship
# N:M Association Table (중간 테이블)
class UserGroupAssociation(Base):
    __tablename__ = "user_group_association"

    user_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("users.id", ondelete="CASCADE"), primary_key=True
    )
    group_id: Mapped[int] = mapped_column(
        Integer, ForeignKey("groups.id", ondelete="CASCADE"), primary_key=True
    )

    # # 관계 정의
    # user: Mapped["User"] = relationship("User", back_populates="user_groups")
    # group: Mapped["Group"] = relationship("Group", back_populates="group_users")
    # # 복합 기본 키 설정

    # 기본 키 설정을 위한 __table_args__ 추가
    __table_args__ = (PrimaryKeyConstraint("user_id", "group_id"),)

    def __repr__(self) -> str:
        return f"UserGroupAssociation(user_id={self.user_id}, group_id={self.group_id})"


# Group 테이블
class Group(Base):
    __tablename__ = "groups"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
    name: Mapped[str] = mapped_column(String(255), nullable=False, unique=True)
    description: Mapped[str] = mapped_column(String(1000))
    is_public: Mapped[bool] = mapped_column(Boolean, default=True)
    created_at: Mapped[DateTime] = mapped_column(DateTime, server_default=func.now())
    updated_at: Mapped[DateTime] = mapped_column(
        DateTime, server_default=func.now(), onupdate=func.now()
    )

    user_group: Mapped[list["User"]] = relationship(
        "User",
        secondary="user_group_association",
        back_populates="group_user",
        lazy="selectin",
    )

    # Group을 만든 사용자와 관계 (일반적인 1:N 관계)
    def __repr__(self) -> str:
        return f"Group(id={self.id}, name={self.name})"

    __table_args__ = (
        Index("idx_groups_name", "name"),
        Index("idx_groups_is_public", "is_public"),
        Index("idx_groups_created_at", "created_at"),
        Index("idx_groups_composite", "is_public", "created_at"),
    )
