from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, MappedAsDataclass
from sqlalchemy import types, text, String, DateTime, func, Boolean, ForeignKey, SmallInteger, Integer
from datetime import datetime
import uuid
from typing import List
from config import *
from constants import UserRole, PublishedStatus

class Base(MappedAsDataclass, DeclarativeBase):
    pass

db = SQLAlchemy(model_class=Base)

class User(db.Model):
    __tablename__ = 'user'

    id: Mapped[uuid.UUID] = mapped_column(types.Uuid, primary_key=True, init=False, server_default=text("gen_random_uuid()"))
    email: Mapped[str] = mapped_column(String(64), nullable=False, unique=True)
    firstName: Mapped[str] = mapped_column(String(32), nullable=False)
    lastName: Mapped[str] = mapped_column(String(32), nullable=False)
    username: Mapped[str] = mapped_column(String(32), nullable=False, unique=True)
    hash_password: Mapped[str] = mapped_column(String(256), nullable=False)
    activationKey: Mapped[str] = mapped_column(String(128), nullable=False)
    sessions: Mapped[List["Session"]] = relationship(back_populates="user", cascade="all, delete-orphan")
    enrollments: Mapped[List["Enrollment"]] = relationship(back_populates="user", cascade="all, delete-orphan")
    quizzes: Mapped[List["Quiz"]] = relationship(back_populates="creatorUser", cascade="all, delete-orphan")
    quiz_attempts: Mapped[List["QuizAttempt"]] = relationship(back_populates="user", cascade="all, delete-orphan")
    chats: Mapped[List["Chat"]] = relationship(back_populates="user", cascade="all, delete-orphan")
    notifications: Mapped[List["Notification"]] = relationship(back_populates="user", cascade="all, delete-orphan")
    user_badges: Mapped[List["UserBadge"]] = relationship(back_populates="user", cascade="all, delete-orphan")
    publications: Mapped[List["Course"]] = relationship(back_populates="author", cascade="all, delete-orphan")
    dob: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=datetime.fromisocalendar(2002, 1, 1))
    pfpFilename: Mapped[str] = mapped_column(String(256), nullable=False, default=DEFAULT_PROFILE_FILE)
    joinedDate: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
    lastOnline: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
    bio: Mapped[str] = mapped_column(String(256), nullable=False, default='')
    role: Mapped[int] = mapped_column(SmallInteger, nullable=False, default=UserRole.USER)
    isActivated: Mapped[bool] = mapped_column(Boolean, nullable=False, default=ACTIVATE_ACCOUNTS_ON_SIGNUP)

class Session(db.Model):
    __tablename__ = 'session'

    id: Mapped[uuid.UUID] = mapped_column(types.Uuid, primary_key=True, init=False, server_default=text("gen_random_uuid()"))
    userID: Mapped[uuid.UUID] = mapped_column(ForeignKey("user.id"))
    user: Mapped["User"] = relationship(back_populates="sessions")
    key: Mapped[str] = mapped_column(String(256), nullable=False, unique=True)
    ua: Mapped[str] = mapped_column(String(1024), nullable=False)
    creationDate: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
    lastUsed: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
    isValid: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)

class Category(db.Model):
    __tablename__ = 'category'

    id: Mapped[uuid.UUID] = mapped_column(types.Uuid, primary_key=True, init=False, server_default=text("gen_random_uuid()"))
    name: Mapped[str] = mapped_column(String(32), nullable=False, unique=True)
    courses: Mapped[List["Course"]] = relationship(back_populates="category", cascade="all, delete-orphan")
    description: Mapped[str] = mapped_column(String(128), nullable=False, default='')
    isActive: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
    creationDate: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())

class Course(db.Model):
    __tablename__ = 'course'

    id: Mapped[uuid.UUID] = mapped_column(types.Uuid, primary_key=True, init=False, server_default=text("gen_random_uuid()"))
    name: Mapped[str] = mapped_column(String(64), nullable=False, unique=True)
    categoryID: Mapped[uuid.UUID] = mapped_column(ForeignKey("category.id"))
    category: Mapped["Category"] = relationship(back_populates="courses")
    enrollments: Mapped[List["Enrollment"]] = relationship(back_populates="course", cascade="all, delete-orphan")
    quizzes: Mapped[List['Quiz']] = relationship(back_populates="course", cascade="all, delete-orphan")
    chats: Mapped[List["Chat"]] = relationship(back_populates="course", cascade="all, delete-orphan")
    authorID: Mapped[uuid.UUID] = mapped_column(ForeignKey("user.id"))
    author: Mapped["User"] = relationship(back_populates="publications")
    description: Mapped[str] = mapped_column(String(1024), nullable=False, default='')
    pageForCommunity: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
    totalPages: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
    totalEnrolled: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
    isActive: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
    publishedStatus: Mapped[int] = mapped_column(SmallInteger, nullable=False, default=int(PublishedStatus.PENDING))
    creationDate: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
    coverImage: Mapped[str] = mapped_column(String(256), nullable=False, default=DEFAULT_COURSE_COVER)
    serverFilename: Mapped[str] = mapped_column(String(256), nullable=False, default='')


class Enrollment(db.Model):
    __tablename__ = 'enrollment'

    id: Mapped[uuid.UUID] = mapped_column(types.Uuid, primary_key=True, init=False, server_default=text("gen_random_uuid()"))
    userID: Mapped[uuid.UUID] = mapped_column(ForeignKey("user.id"))
    user: Mapped["User"] = relationship(back_populates="enrollments")
    courseID: Mapped[uuid.UUID] = mapped_column(ForeignKey("course.id"))
    course: Mapped["Course"] = relationship(back_populates="enrollments")
    joinedDate: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
    lastActivity: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now(), onupdate=func.now())
    currentPage: Mapped[int] = mapped_column(nullable=False, default=1)
    maxPage: Mapped[int] = mapped_column(nullable=False, default=1)

class Quiz(db.Model):
    __tablename__ = 'quiz'

    id: Mapped[uuid.UUID] = mapped_column(types.Uuid, primary_key=True, init=False, server_default=text("gen_random_uuid()"))
    creatorUserID: Mapped[uuid.UUID] = mapped_column(ForeignKey("user.id"))
    creatorUser: Mapped["User"] = relationship(back_populates="quizzes")
    quiz_attempts: Mapped[List["QuizAttempt"]] = relationship(back_populates="quiz", cascade="all, delete-orphan")
    courseID: Mapped[uuid.UUID] = mapped_column(ForeignKey("course.id"))
    course: Mapped["Course"] = relationship(back_populates="quizzes")
    quizQuestion: Mapped[str] = mapped_column(String, nullable=False)
    quizAnswers: Mapped[str] = mapped_column(String, nullable=False)
    quizCorrectAnswer: Mapped[str] = mapped_column(String, nullable=False)
    creationDate: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
    isActive: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
    creatorHasAttempted: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)

class QuizAttempt(db.Model):
    __tablename__ = 'quiz_attempts'

    id: Mapped[uuid.UUID] = mapped_column(types.Uuid, primary_key=True, init=False, server_default=text("gen_random_uuid()"))
    userID: Mapped[uuid.UUID] = mapped_column(ForeignKey("user.id"))
    user: Mapped["User"] = relationship(back_populates="quiz_attempts")
    quizID: Mapped[uuid.UUID] = mapped_column(ForeignKey("quiz.id"))
    quiz: Mapped["Quiz"] = relationship(back_populates="quiz_attempts")
    userAnswer: Mapped[str] = mapped_column(String, nullable=False)
    isCorrect: Mapped[int] = mapped_column(default=0, nullable=False)
    attemptDate: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())

class Chat(db.Model):
    __tablename__ = 'chat'

    id: Mapped[uuid.UUID] = mapped_column(types.Uuid, primary_key=True, init=False, server_default=text("gen_random_uuid()"))
    textContent: Mapped[str] = mapped_column(nullable=False)
    userID: Mapped[uuid.UUID] = mapped_column(ForeignKey("user.id"))
    courseID: Mapped[uuid.UUID] = mapped_column(ForeignKey("course.id"))
    course: Mapped["Course"] = relationship(back_populates="chats")
    user: Mapped["User"] = relationship(back_populates="chats")
    chatDate: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())

class Notification(db.Model):
    __tablename__ = 'notification'

    id: Mapped[uuid.UUID] = mapped_column(types.Uuid, primary_key=True, init=False, server_default=text("gen_random_uuid()"))
    userID: Mapped[uuid.UUID] = mapped_column(ForeignKey("user.id"))
    user: Mapped["User"] = relationship(back_populates="notifications")
    notificationType: Mapped[int] = mapped_column(SmallInteger, nullable=False)
    notificationData: Mapped[str] = mapped_column(String, nullable=False)
    isSeen: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
    notifDate: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())

class Badge(db.Model):
    __tablename__ = 'badge'

    id: Mapped[uuid.UUID] = mapped_column(types.Uuid, primary_key=True, init=False, server_default=text("gen_random_uuid()"))
    name: Mapped[str] = mapped_column(String(16), nullable=False)
    user_badges: Mapped[List["UserBadge"]] = relationship(back_populates="badge", cascade="all, delete-orphan")
    description: Mapped[str] = mapped_column(String(256), nullable=False, default='')
    createDate: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
    icon: Mapped[str] = mapped_column(String(256), nullable=False, default='')
    canClaim: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)

class UserBadge(db.Model):
    __tablename__ = 'userbadge'
    id: Mapped[uuid.UUID] = mapped_column(types.Uuid, primary_key=True, init=False, server_default=text("gen_random_uuid()"))
    userID: Mapped[uuid.UUID] = mapped_column(ForeignKey("user.id"))
    badgeID: Mapped[uuid.UUID] = mapped_column(ForeignKey("badge.id"))
    user: Mapped["User"] = relationship(back_populates="user_badges")
    badge: Mapped["Badge"] = relationship(back_populates="user_badges")
    claimedDate: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=func.now())
