from __future__ import annotations

import uuid
from datetime import UTC, datetime

from sqlalchemy import (
    Boolean,
    CheckConstraint,
    DateTime,
    ForeignKey,
    Integer,
    Numeric,
    String,
    Text,
)
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship


class Base(DeclarativeBase):
    pass


def uuid_pk() -> Mapped[uuid.UUID]:
    return mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)


def now() -> datetime:
    return datetime.now(UTC)


class TimestampMixin:
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=now)
    updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=now, onupdate=now)


class Equipment(Base, TimestampMixin):
    __tablename__ = "logic_equipment"

    id: Mapped[uuid.UUID] = uuid_pk()
    owner_user_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), index=True)
    name: Mapped[str] = mapped_column(String(160), index=True)
    description: Mapped[str | None] = mapped_column(Text)
    image_s3_url: Mapped[str | None] = mapped_column(Text)
    image_s3_key: Mapped[str | None] = mapped_column(Text)
    is_builtin: Mapped[bool] = mapped_column(Boolean, default=False, index=True)

    workout_items: Mapped[list[WorkoutItem]] = relationship(back_populates="equipment")


class ActivitySource(Base, TimestampMixin):
    __tablename__ = "logic_activity_sources"
    __table_args__ = (
        CheckConstraint("kind IN ('exercise', 'machine')", name="ck_activity_source_kind"),
        CheckConstraint(
            "category IN ('chest', 'back', 'legs', 'shoulders', 'biceps', 'triceps', "
            "'core', 'cardio', 'full_body', 'other')",
            name="ck_activity_source_category",
        ),
        CheckConstraint(
            "equipment IN ('barbell', 'dumbbell', 'machine', 'cable', 'bodyweight', "
            "'kettlebell', 'cardio_machine', 'other')",
            name="ck_activity_source_equipment",
        ),
        CheckConstraint(
            "measurement_type IN ('weight_reps', 'reps_only', 'duration', "
            "'distance_duration', 'duration_calories')",
            name="ck_activity_source_measurement_type",
        ),
        CheckConstraint(
            "difficulty IN ('beginner', 'intermediate', 'advanced')",
            name="ck_activity_source_difficulty",
        ),
    )

    id: Mapped[uuid.UUID] = uuid_pk()
    owner_user_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), index=True)
    slug: Mapped[str] = mapped_column(String(180), unique=True, index=True)
    kind: Mapped[str] = mapped_column(String(20), index=True)
    title: Mapped[str] = mapped_column(String(160), index=True)
    description: Mapped[str | None] = mapped_column(Text)
    category: Mapped[str] = mapped_column(String(32), index=True)
    equipment: Mapped[str] = mapped_column(String(32), index=True)
    measurement_type: Mapped[str] = mapped_column(String(32))
    difficulty: Mapped[str] = mapped_column(String(20), default="intermediate")
    image_s3_url: Mapped[str | None] = mapped_column(Text)
    image_s3_key: Mapped[str | None] = mapped_column(Text)
    is_builtin: Mapped[bool] = mapped_column(Boolean, default=False, index=True)
    default_calories_per_minute: Mapped[float | None] = mapped_column(Numeric(8, 2))

    workout_items: Mapped[list[WorkoutItem]] = relationship(back_populates="activity_source")


class Exercise(Base, TimestampMixin):
    __tablename__ = "logic_exercises"

    id: Mapped[uuid.UUID] = uuid_pk()
    owner_user_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), index=True)
    equipment_id: Mapped[uuid.UUID | None] = mapped_column(ForeignKey("logic_equipment.id"))
    name: Mapped[str] = mapped_column(String(160), index=True)
    description: Mapped[str | None] = mapped_column(Text)
    image_s3_url: Mapped[str | None] = mapped_column(Text)
    image_s3_key: Mapped[str | None] = mapped_column(Text)
    is_builtin: Mapped[bool] = mapped_column(Boolean, default=False, index=True)
    default_calories_per_minute: Mapped[float | None] = mapped_column(Numeric(8, 2))

    equipment: Mapped[Equipment | None] = relationship()
    workout_items: Mapped[list[WorkoutItem]] = relationship(back_populates="exercise")


class Workout(Base, TimestampMixin):
    __tablename__ = "logic_workouts"
    __table_args__ = (
        CheckConstraint(
            "status IN ('active', 'finished', 'discarded')",
            name="ck_workout_status",
        ),
    )

    id: Mapped[uuid.UUID] = uuid_pk()
    user_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), index=True)
    status: Mapped[str] = mapped_column(String(20), default="active", index=True)
    started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=now)
    finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
    notes: Mapped[str | None] = mapped_column(Text)
    total_sets: Mapped[int] = mapped_column(Integer, default=0)
    total_volume: Mapped[float] = mapped_column(Numeric(12, 2), default=0)
    estimated_calories: Mapped[float] = mapped_column(Numeric(10, 2), default=0)

    items: Mapped[list[WorkoutItem]] = relationship(
        back_populates="workout", cascade="all, delete-orphan", order_by="WorkoutItem.order_index"
    )


class WorkoutItem(Base):
    __tablename__ = "logic_workout_items"
    __table_args__ = (
        CheckConstraint(
            "(CASE WHEN activity_source_id IS NOT NULL THEN 1 ELSE 0 END + "
            "CASE WHEN exercise_id IS NOT NULL THEN 1 ELSE 0 END + "
            "CASE WHEN equipment_id IS NOT NULL THEN 1 ELSE 0 END) = 1",
            name="ck_workout_item_exactly_one_entity",
        ),
        CheckConstraint(
            "source_kind IN ('exercise', 'machine', 'equipment')",
            name="ck_workout_item_source_kind",
        ),
    )

    id: Mapped[uuid.UUID] = uuid_pk()
    workout_id: Mapped[uuid.UUID] = mapped_column(
        ForeignKey("logic_workouts.id", ondelete="CASCADE")
    )
    source_kind: Mapped[str] = mapped_column(String(20))
    title_snapshot: Mapped[str] = mapped_column(String(160))
    image_s3_url_snapshot: Mapped[str | None] = mapped_column(Text)
    measurement_type_snapshot: Mapped[str] = mapped_column(String(32), default="weight_reps")
    category_snapshot: Mapped[str] = mapped_column(String(32), default="other")
    equipment_snapshot: Mapped[str] = mapped_column(String(32), default="other")
    activity_source_id: Mapped[uuid.UUID | None] = mapped_column(
        ForeignKey("logic_activity_sources.id")
    )
    exercise_id: Mapped[uuid.UUID | None] = mapped_column(ForeignKey("logic_exercises.id"))
    equipment_id: Mapped[uuid.UUID | None] = mapped_column(ForeignKey("logic_equipment.id"))
    order_index: Mapped[int] = mapped_column(Integer, default=0)
    planned_working_weight: Mapped[float | None] = mapped_column(Numeric(8, 2))
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=now)

    workout: Mapped[Workout] = relationship(back_populates="items")
    activity_source: Mapped[ActivitySource | None] = relationship(back_populates="workout_items")
    exercise: Mapped[Exercise | None] = relationship(back_populates="workout_items")
    equipment: Mapped[Equipment | None] = relationship(back_populates="workout_items")
    sets: Mapped[list[WorkoutSet]] = relationship(
        back_populates="workout_item", cascade="all, delete-orphan", order_by="WorkoutSet.set_index"
    )


class WorkoutSet(Base):
    __tablename__ = "logic_workout_sets"

    id: Mapped[uuid.UUID] = uuid_pk()
    workout_item_id: Mapped[uuid.UUID] = mapped_column(
        ForeignKey("logic_workout_items.id", ondelete="CASCADE")
    )
    set_index: Mapped[int] = mapped_column(Integer)
    weight: Mapped[float] = mapped_column(Numeric(8, 2), default=0)
    reps: Mapped[int] = mapped_column(Integer, default=0)
    duration_seconds: Mapped[int | None] = mapped_column(Integer)
    distance_km: Mapped[float | None] = mapped_column(Numeric(8, 3))
    calories: Mapped[float | None] = mapped_column(Numeric(8, 2))
    completed_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=now)

    workout_item: Mapped[WorkoutItem] = relationship(back_populates="sets")
