from typing import List, Optional from sqlmodel import Field, Relationship, SQLModel from datetime import datetime # --- Join Tables --- class Performance(SQLModel, table=True): """Link table between Show and Song (Many-to-Many with extra data)""" id: Optional[int] = Field(default=None, primary_key=True) show_id: int = Field(foreign_key="show.id") song_id: int = Field(foreign_key="song.id") position: int = Field(description="Order in the setlist") set_name: Optional[str] = Field(default=None, description="e.g., Set 1, Encore") segue: bool = Field(default=False, description="Transition to next song >") notes: Optional[str] = Field(default=None) nicknames: List["PerformanceNickname"] = Relationship(back_populates="performance") show: "Show" = Relationship(back_populates="performances") song: "Song" = Relationship() class ShowArtist(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) show_id: int = Field(foreign_key="show.id") artist_id: int = Field(foreign_key="artist.id") notes: Optional[str] = Field(default=None, description="Role e.g. Guest") class PerformanceArtist(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) performance_id: int = Field(foreign_key="performance.id") artist_id: int = Field(foreign_key="artist.id") notes: Optional[str] = Field(default=None, description="Role e.g. Guest") class PerformanceNickname(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) performance_id: int = Field(foreign_key="performance.id") nickname: str = Field(index=True) description: Optional[str] = Field(default=None) status: str = Field(default="pending", index=True) # pending, approved, rejected suggested_by: int = Field(foreign_key="user.id") created_at: datetime = Field(default_factory=datetime.utcnow) performance: "Performance" = Relationship(back_populates="nicknames") user: "User" = Relationship() class EntityTag(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) tag_id: int = Field(foreign_key="tag.id") entity_type: str = Field(index=True) # "show", "song", "venue" entity_id: int = Field(index=True) # --- Core Entities --- class Vertical(SQLModel, table=True): """Represents a Fandom Vertical (e.g., 'Phish', 'Goose', 'Star Wars')""" id: Optional[int] = Field(default=None, primary_key=True) name: str = Field(index=True) slug: str = Field(unique=True, index=True) description: Optional[str] = Field(default=None) shows: List["Show"] = Relationship(back_populates="vertical") songs: List["Song"] = Relationship(back_populates="vertical") class Venue(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) name: str = Field(index=True) city: str state: Optional[str] = Field(default=None) country: str capacity: Optional[int] = Field(default=None) notes: Optional[str] = Field(default=None) shows: List["Show"] = Relationship(back_populates="venue") class Tour(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) name: str = Field(index=True) start_date: Optional[datetime] = None end_date: Optional[datetime] = None notes: Optional[str] = Field(default=None) shows: List["Show"] = Relationship(back_populates="tour") class Artist(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) name: str = Field(index=True) instrument: Optional[str] = Field(default=None) notes: Optional[str] = Field(default=None) class Show(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) date: datetime = Field(index=True) vertical_id: int = Field(foreign_key="vertical.id") venue_id: Optional[int] = Field(default=None, foreign_key="venue.id") tour_id: Optional[int] = Field(default=None, foreign_key="tour.id") notes: Optional[str] = Field(default=None) vertical: Vertical = Relationship(back_populates="shows") venue: Optional[Venue] = Relationship(back_populates="shows") tour: Optional[Tour] = Relationship(back_populates="shows") attendances: List["Attendance"] = Relationship(back_populates="show") performances: List["Performance"] = Relationship(back_populates="show") class Song(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) title: str = Field(index=True) original_artist: Optional[str] = Field(default=None) vertical_id: int = Field(foreign_key="vertical.id") notes: Optional[str] = Field(default=None) vertical: Vertical = Relationship(back_populates="songs") class Tag(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) name: str = Field(unique=True, index=True) slug: str = Field(unique=True, index=True) class Attendance(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) user_id: int = Field(foreign_key="user.id") show_id: int = Field(foreign_key="show.id") notes: Optional[str] = Field(default=None) created_at: datetime = Field(default_factory=datetime.utcnow) user: "User" = Relationship(back_populates="attendances") show: "Show" = Relationship(back_populates="attendances") class Comment(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) user_id: int = Field(foreign_key="user.id") content: str created_at: datetime = Field(default_factory=datetime.utcnow) # Polymorphic-ish associations (nullable FKs) show_id: Optional[int] = Field(default=None, foreign_key="show.id") venue_id: Optional[int] = Field(default=None, foreign_key="venue.id") song_id: Optional[int] = Field(default=None, foreign_key="song.id") user: "User" = Relationship(back_populates="comments") class Rating(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) user_id: int = Field(foreign_key="user.id") score: int = Field(ge=1, le=10, description="Rating from 1 to 10") created_at: datetime = Field(default_factory=datetime.utcnow) show_id: Optional[int] = Field(default=None, foreign_key="show.id") song_id: Optional[int] = Field(default=None, foreign_key="song.id") performance_id: Optional[int] = Field(default=None, foreign_key="performance.id") user: "User" = Relationship(back_populates="ratings") class User(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) email: str = Field(unique=True, index=True) hashed_password: str is_active: bool = Field(default=True) is_superuser: bool = Field(default=False) role: str = Field(default="user") # user, moderator, admin bio: Optional[str] = Field(default=None) avatar: Optional[str] = Field(default=None) # Multi-identity support: A user can have multiple Profiles profiles: List["Profile"] = Relationship(back_populates="user") comments: List["Comment"] = Relationship(back_populates="user") ratings: List["Rating"] = Relationship(back_populates="user") reviews: List["Review"] = Relationship(back_populates="user") attendances: List["Attendance"] = Relationship(back_populates="user") badges: List["UserBadge"] = Relationship(back_populates="user") preferences: Optional["UserPreferences"] = Relationship(back_populates="user", sa_relationship_kwargs={"uselist": False}) reports: List["Report"] = Relationship(back_populates="user") notifications: List["Notification"] = Relationship(back_populates="user") class Report(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) user_id: int = Field(foreign_key="user.id") entity_type: str = Field(index=True) # comment, review, nickname entity_id: int = Field(index=True) reason: str details: str = Field(default="") status: str = Field(default="pending", index=True) # pending, resolved, dismissed created_at: datetime = Field(default_factory=datetime.utcnow) user: "User" = Relationship(back_populates="reports") class Badge(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) name: str = Field(unique=True, index=True) description: str icon: str = Field(description="Lucide icon name or image URL") slug: str = Field(unique=True, index=True) class UserBadge(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) user_id: int = Field(foreign_key="user.id") badge_id: int = Field(foreign_key="badge.id") awarded_at: datetime = Field(default_factory=datetime.utcnow) user: "User" = Relationship(back_populates="badges") badge: "Badge" = Relationship() class Review(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) user_id: int = Field(foreign_key="user.id") blurb: str = Field(description="One-liner/pullquote") content: str = Field(description="Full review text") score: int = Field(ge=1, le=10) show_id: Optional[int] = Field(default=None, foreign_key="show.id") venue_id: Optional[int] = Field(default=None, foreign_key="venue.id") song_id: Optional[int] = Field(default=None, foreign_key="song.id") performance_id: Optional[int] = Field(default=None, foreign_key="performance.id") tour_id: Optional[int] = Field(default=None, foreign_key="tour.id") year: Optional[int] = Field(default=None, description="For reviewing a specific year") created_at: datetime = Field(default_factory=datetime.utcnow) user: "User" = Relationship(back_populates="reviews") class UserPreferences(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) user_id: int = Field(foreign_key="user.id", unique=True) wiki_mode: bool = Field(default=False, description="Disable social features") show_ratings: bool = Field(default=True) show_comments: bool = Field(default=True) user: User = Relationship(back_populates="preferences") class Profile(SQLModel, table=True): """A user's identity within a specific context or global""" id: Optional[int] = Field(default=None, primary_key=True) user_id: int = Field(foreign_key="user.id") username: str = Field(index=True) display_name: Optional[str] = Field(default=None) user: User = Relationship(back_populates="profiles") # --- Groups --- class Group(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) name: str = Field(index=True, unique=True) description: Optional[str] = None privacy: str = Field(default="public") # public, private created_by: int = Field(foreign_key="user.id") created_at: datetime = Field(default_factory=datetime.utcnow) members: List["GroupMember"] = Relationship(back_populates="group") posts: List["GroupPost"] = Relationship(back_populates="group") class GroupMember(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) group_id: int = Field(foreign_key="group.id") user_id: int = Field(foreign_key="user.id") role: str = Field(default="member") # member, admin joined_at: datetime = Field(default_factory=datetime.utcnow) group: Group = Relationship(back_populates="members") user: User = Relationship() class GroupPost(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) group_id: int = Field(foreign_key="group.id") user_id: int = Field(foreign_key="user.id") content: str created_at: datetime = Field(default_factory=datetime.utcnow) group: Group = Relationship(back_populates="posts") user: User = Relationship() class Notification(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) user_id: int = Field(foreign_key="user.id", index=True) type: str = Field(description="reply, mention, system") title: str message: str link: Optional[str] = None is_read: bool = Field(default=False) created_at: datetime = Field(default_factory=datetime.utcnow) user: User = Relationship(back_populates="notifications")