import typing
from datetime import date, datetime

from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
from sqlalchemy.types import Text


class Base(DeclarativeBase):
    pass


WorkTypes = typing.Literal["cooking", "dishes", "tables"]


class Event(Base):
    __tablename__ = "event"
    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    title: Mapped[str] = mapped_column(nullable=False)
    event_time: Mapped[datetime] = mapped_column(nullable=False)
    registration_deadline: Mapped[datetime] = mapped_column(nullable=False)
    description: Mapped[str] = mapped_column(nullable=True)
    recipe_link: Mapped[str] = mapped_column(nullable=True)

    # Min and max number of people needed for cooking, doing the dishes and preparing the tables
    team_cooking_min: Mapped[int] = mapped_column(default=3, nullable=False)
    team_cooking_max: Mapped[int] = mapped_column(default=5, nullable=False)

    team_dishes_min: Mapped[int] = mapped_column(default=3, nullable=False)
    team_dishes_max: Mapped[int] = mapped_column(default=5, nullable=False)

    # Todo: Rename to "table"
    team_prep_min: Mapped[int] = mapped_column(default=1, nullable=False)
    team_prep_max: Mapped[int] = mapped_column(default=1, nullable=False)

    subscriptions_applied: Mapped[bool] = mapped_column(default=False, nullable=False)
    ignore_subscriptions: Mapped[bool] = mapped_column(default=False, nullable=False)

    billed: Mapped[bool] = mapped_column(default=False, nullable=False)
    exclude_from_billing: Mapped[bool] = mapped_column(default=False, nullable=False)

    organizer_name: Mapped[str] = mapped_column(nullable=True)

    registrations: Mapped[list["Registration"]] = relationship(
        "Registration", cascade="all, delete"
    )
    team: Mapped[list["TeamRegistration"]] = relationship(
        "TeamRegistration", cascade="all, delete"
    )

    def team_min_reached(self, work_type: WorkTypes):
        threshold = {
            "cooking": self.team_cooking_min,
            "dishes": self.team_dishes_min,
            "tables": self.team_prep_min,
        }[work_type]
        return sum(1 for t in self.team if t.work_type == work_type) >= threshold

    def team_max_reached(self, work_type: WorkTypes):
        threshold = {
            "cooking": self.team_cooking_max,
            "dishes": self.team_dishes_max,
            "tables": self.team_prep_max,
        }[work_type]
        return sum(1 for t in self.team if t.work_type == work_type) >= threshold

    def all_teams_min(self):
        return all(
            self.team_min_reached(work_type) for work_type in typing.get_args(WorkTypes)
        )

    def all_teams_max(self):
        return all(
            self.team_max_reached(work_type) for work_type in typing.get_args(WorkTypes)
        )

    @property
    def registration_open(self):
        return datetime.now() < self.registration_deadline

    @property
    def in_the_past(self):
        return datetime.now() > self.event_time


class TeamRegistration(Base):
    __tablename__ = "teamregistration"
    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    event_id: Mapped[int] = mapped_column(ForeignKey("event.id"))
    person_name: Mapped[str] = mapped_column(nullable=False)
    work_type: Mapped[WorkTypes] = mapped_column(Text, nullable=False)
    comment: Mapped[str | None] = mapped_column()


class Household(Base):
    __tablename__ = "household"
    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    name: Mapped[str] = mapped_column(nullable=False)


class Registration(Base):
    __tablename__ = "registration"
    event_id: Mapped[int] = mapped_column(ForeignKey("event.id"), primary_key=True)
    household_id: Mapped[int] = mapped_column(
        ForeignKey("household.id"), primary_key=True
    )
    num_adult_meals: Mapped[int] = mapped_column(nullable=False)
    num_children_meals: Mapped[int] = mapped_column(nullable=False)
    num_small_children_meals: Mapped[int] = mapped_column(nullable=False)
    comment: Mapped[str | None] = mapped_column()

    household: Mapped["Household"] = relationship()


class Subscription(Base):
    __tablename__ = "subscription"
    household_id: Mapped[int] = mapped_column(
        ForeignKey("household.id"), primary_key=True
    )
    num_adult_meals: Mapped[int] = mapped_column(nullable=False)
    num_children_meals: Mapped[int] = mapped_column(nullable=False)
    num_small_children_meals: Mapped[int] = mapped_column(nullable=False)
    comment: Mapped[str | None] = mapped_column()

    last_modified: Mapped[datetime] = mapped_column(
        default=datetime.now, nullable=False
    )

    monday: Mapped[bool] = mapped_column(default=True, nullable=False)
    tuesday: Mapped[bool] = mapped_column(default=True, nullable=False)
    wednesday: Mapped[bool] = mapped_column(default=True, nullable=False)
    thursday: Mapped[bool] = mapped_column(default=True, nullable=False)
    friday: Mapped[bool] = mapped_column(default=True, nullable=False)
    saturday: Mapped[bool] = mapped_column(default=True, nullable=False)
    sunday: Mapped[bool] = mapped_column(default=True, nullable=False)

    household: Mapped["Household"] = relationship()

    def day_string_de(self) -> str:
        """
        Generates a string representation of selected days in German short form.
        """
        result = []

        if self.monday:
            result.append("Mo")
        if self.tuesday:
            result.append("Di")
        if self.wednesday:
            result.append("Mi")
        if self.thursday:
            result.append("Do")
        if self.friday:
            result.append("Fr")
        if self.saturday:
            result.append("Sa")
        if self.sunday:
            result.append("So")

        if len(result) < 7:
            return ", ".join(result)
        else:
            return "Alle"

    def valid_on(self, date: date) -> bool:
        weekday = date.weekday()
        return {
            0: self.monday,
            1: self.tuesday,
            2: self.wednesday,
            3: self.thursday,
            4: self.friday,
            5: self.saturday,
            6: self.sunday,
        }[weekday]
