from __future__ import annotations

import uuid
from datetime import datetime
from typing import Literal

from pydantic import BaseModel, ConfigDict, Field, model_validator

ActivityKind = Literal["exercise", "machine"]
ActivityCategory = Literal[
    "chest",
    "back",
    "legs",
    "shoulders",
    "biceps",
    "triceps",
    "core",
    "cardio",
    "full_body",
    "other",
]
ActivityEquipment = Literal[
    "barbell",
    "dumbbell",
    "machine",
    "cable",
    "bodyweight",
    "kettlebell",
    "cardio_machine",
    "other",
]
MeasurementType = Literal[
    "weight_reps",
    "reps_only",
    "duration",
    "distance_duration",
    "duration_calories",
]
Difficulty = Literal["beginner", "intermediate", "advanced"]


class ActivitySourceCreate(BaseModel):
    slug: str | None = Field(default=None, min_length=1, max_length=180)
    kind: ActivityKind
    title: str = Field(min_length=1, max_length=160)
    description: str | None = None
    category: ActivityCategory = "other"
    equipment: ActivityEquipment = "other"
    measurement_type: MeasurementType = "weight_reps"
    difficulty: Difficulty = "intermediate"
    image_s3_url: str | None = None
    image_s3_key: str | None = None
    default_calories_per_minute: float | None = None


class ActivitySourceRead(ActivitySourceCreate):
    id: uuid.UUID
    slug: str
    owner_user_id: uuid.UUID | None
    is_builtin: bool
    created_at: datetime
    updated_at: datetime

    model_config = ConfigDict(from_attributes=True)


class EquipmentCreate(BaseModel):
    name: str = Field(min_length=1, max_length=160)
    description: str | None = None
    image_s3_url: str | None = None
    image_s3_key: str | None = None


class EquipmentRead(EquipmentCreate):
    id: uuid.UUID
    owner_user_id: uuid.UUID | None
    is_builtin: bool
    created_at: datetime
    updated_at: datetime

    model_config = ConfigDict(from_attributes=True)


class ExerciseCreate(BaseModel):
    name: str = Field(min_length=1, max_length=160)
    description: str | None = None
    equipment_id: uuid.UUID | None = None
    image_s3_url: str | None = None
    image_s3_key: str | None = None
    default_calories_per_minute: float | None = None


class ExerciseRead(ExerciseCreate):
    id: uuid.UUID
    owner_user_id: uuid.UUID | None
    is_builtin: bool
    created_at: datetime
    updated_at: datetime

    model_config = ConfigDict(from_attributes=True)


class WorkoutCreate(BaseModel):
    started_at: datetime | None = None
    notes: str | None = None


class WorkoutUpdate(BaseModel):
    finished_at: datetime | None = None
    notes: str | None = None


class WorkoutFinishRequest(BaseModel):
    notes: str | None = None


class WorkoutSetCreate(BaseModel):
    weight: float = Field(default=0, ge=0)
    reps: int = Field(default=0, ge=0)
    duration_seconds: int | None = None
    distance_km: float | None = Field(default=None, ge=0)
    calories: float | None = None
    completed_at: datetime | None = None


class WorkoutSetBatchCreate(BaseModel):
    sets: list[WorkoutSetCreate] = Field(min_length=1)


class WorkoutSetUpdate(BaseModel):
    weight: float | None = Field(default=None, ge=0)
    reps: int | None = Field(default=None, ge=0)
    duration_seconds: int | None = None
    distance_km: float | None = Field(default=None, ge=0)
    calories: float | None = None
    completed_at: datetime | None = None

    @model_validator(mode="after")
    def at_least_one_field(self) -> WorkoutSetUpdate:
        if not self.model_fields_set:
            raise ValueError("Provide at least one field to update")
        return self


class WorkoutSetRead(WorkoutSetCreate):
    id: uuid.UUID
    workout_item_id: uuid.UUID
    set_index: int
    completed_at: datetime

    model_config = ConfigDict(from_attributes=True)


class WorkoutItemCreate(BaseModel):
    activity_source_id: uuid.UUID | None = None
    exercise_id: uuid.UUID | None = None
    equipment_id: uuid.UUID | None = None
    order_index: int | None = None
    planned_working_weight: float | None = None

    @model_validator(mode="after")
    def exactly_one_entity(self) -> WorkoutItemCreate:
        provided = [self.activity_source_id, self.exercise_id, self.equipment_id]
        if sum(value is not None for value in provided) != 1:
            raise ValueError("Provide exactly one activity source, exercise, or equipment id")
        return self


class WorkoutItemRead(WorkoutItemCreate):
    id: uuid.UUID
    workout_id: uuid.UUID
    source_kind: Literal["exercise", "machine", "equipment"]
    title_snapshot: str
    image_s3_url_snapshot: str | None
    measurement_type_snapshot: MeasurementType
    category_snapshot: ActivityCategory
    equipment_snapshot: ActivityEquipment
    order_index: int
    created_at: datetime
    sets: list[WorkoutSetRead] = []

    model_config = ConfigDict(from_attributes=True)


class WorkoutRead(BaseModel):
    id: uuid.UUID
    user_id: uuid.UUID
    status: Literal["active", "finished", "discarded"]
    started_at: datetime
    finished_at: datetime | None
    notes: str | None
    total_sets: int
    total_volume: float
    estimated_calories: float
    created_at: datetime
    updated_at: datetime
    items: list[WorkoutItemRead] = []

    model_config = ConfigDict(from_attributes=True)


class ProgressionPoint(BaseModel):
    date: str
    max_weight: float
    volume: float


class ProgressionRead(BaseModel):
    last_weight: float | None
    max_weight: float | None
    previous_delta: float | None
    points: list[ProgressionPoint]


class CaloriesRead(BaseModel):
    total_calories: float
    workouts: list[dict[str, str | float]]
