from datetime import datetime from pydantic import BaseModel, Field from typing import Literal from app.models.event import EventType from app.schemas.user import UserPublic # Event type literals for Pydantic EventTypeLiteral = Literal[ "golden_hour", "common_enemy", "double_risk", "jackpot", "swap", "rematch", ] class EventCreate(BaseModel): type: EventTypeLiteral duration_minutes: int | None = Field( None, description="Duration in minutes. If not provided, uses default for event type." ) challenge_id: int | None = Field( None, description="For common_enemy event - the challenge everyone will get" ) class EventEffects(BaseModel): points_multiplier: float = 1.0 drop_free: bool = False special_action: str | None = None # "swap", "rematch" description: str = "" class EventResponse(BaseModel): id: int type: EventTypeLiteral start_time: datetime end_time: datetime | None is_active: bool created_by: UserPublic | None data: dict | None created_at: datetime class Config: from_attributes = True class ActiveEventResponse(BaseModel): event: EventResponse | None effects: EventEffects time_remaining_seconds: int | None = None class SwapRequest(BaseModel): target_participant_id: int class CommonEnemyLeaderboard(BaseModel): participant_id: int user: UserPublic completed_at: datetime | None rank: int | None bonus_points: int # Event descriptions and default durations EVENT_INFO = { EventType.GOLDEN_HOUR: { "name": "Золотой час", "description": "Все очки x1.5!", "default_duration": 45, "points_multiplier": 1.5, "drop_free": False, }, EventType.COMMON_ENEMY: { "name": "Общий враг", "description": "Все получают одинаковый челлендж. Первые 3 получают бонус!", "default_duration": None, # Until all complete "points_multiplier": 1.0, "drop_free": False, }, EventType.DOUBLE_RISK: { "name": "Двойной риск", "description": "Дропы бесплатны, но очки x0.5", "default_duration": 120, "points_multiplier": 0.5, "drop_free": True, }, EventType.JACKPOT: { "name": "Джекпот", "description": "Следующий спин — сложный челлендж с x3 очками!", "default_duration": None, # 1 spin "points_multiplier": 3.0, "drop_free": False, }, EventType.SWAP: { "name": "Обмен", "description": "Можно поменяться заданием с другим участником", "default_duration": 60, "points_multiplier": 1.0, "drop_free": False, "special_action": "swap", }, EventType.REMATCH: { "name": "Реванш", "description": "Можно переделать проваленный челлендж за 50% очков", "default_duration": 240, "points_multiplier": 0.5, "drop_free": False, "special_action": "rematch", }, } # Bonus points for Common Enemy top 3 COMMON_ENEMY_BONUSES = { 1: 50, 2: 30, 3: 15, } class SwapCandidate(BaseModel): """Participant available for assignment swap""" participant_id: int user: UserPublic challenge_title: str challenge_description: str challenge_points: int challenge_difficulty: str game_title: str # Two-sided swap confirmation schemas SwapRequestStatusLiteral = Literal["pending", "accepted", "declined", "cancelled"] class SwapRequestCreate(BaseModel): """Request to swap assignment with another participant""" target_participant_id: int class SwapRequestChallengeInfo(BaseModel): """Challenge info for swap request display""" title: str description: str points: int difficulty: str game_title: str class SwapRequestResponse(BaseModel): """Response for a swap request""" id: int status: SwapRequestStatusLiteral from_user: UserPublic to_user: UserPublic from_challenge: SwapRequestChallengeInfo to_challenge: SwapRequestChallengeInfo created_at: datetime responded_at: datetime | None class Config: from_attributes = True class MySwapRequests(BaseModel): """User's incoming and outgoing swap requests""" incoming: list[SwapRequestResponse] outgoing: list[SwapRequestResponse]