from datetime import date, datetime, time, timezone
from enum import Enum

from sqlmodel import Field, SQLModel, UniqueConstraint


class TaskKind(str, Enum):
    required = "required"
    bonus = "bonus"
    study = "study"


class TaskStatus(str, Enum):
    open = "open"
    done = "done"
    cancelled = "cancelled"


class CoinTransactionType(str, Enum):
    earned = "earned"
    penalty = "penalty"
    penalty_reversal = "penalty_reversal"
    manual = "manual"
    reserve = "reserve"
    release = "release"
    capture = "capture"
    undo = "undo"
    challenge_reward = "challenge_reward"


class BenefitRedemptionStatus(str, Enum):
    reserved = "reserved"
    fulfilled = "fulfilled"
    cancelled = "cancelled"


class ScreenTimeSessionStatus(str, Enum):
    active = "active"
    stopped = "stopped"


class ExamStatus(str, Enum):
    planned = "planned"
    cancelled = "cancelled"
    done = "done"


class SpecialDayType(str, Enum):
    holiday = "holiday"
    no_school = "no_school"
    sick = "sick"
    special = "special"


class ScheduleBlockType(str, Enum):
    school_subject = "school_subject"
    external_activity = "external_activity"
    music = "music"
    sports = "sports"
    free = "free"
    other = "other"


class ChildSettings(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("child_id", name="uq_child_settings_child_id"),)

    id: int | None = Field(default=None, primary_key=True)
    child_id: int = Field(foreign_key="child.id", index=True)
    default_wake_time: time = Field(default=time(6, 45))
    default_school_start: time = Field(default=time(8, 15))
    commute_minutes: int = Field(default=15, ge=0)
    bedtime: time = Field(default=time(20, 15))
    language_quiz_language: str = Field(default="en", max_length=8)
    language_quiz_level: int = Field(default=2, ge=1, le=4)
    math_quiz_level: int = Field(default=2, ge=1, le=5)
    knowledge_quiz_level: int = Field(default=2, ge=1, le=4)


class ScheduleTimeSlot(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("label", name="uq_schedule_time_slot_label"),)

    id: int | None = Field(default=None, primary_key=True)
    label: str = Field(index=True)
    start_time: time | None = None
    end_time: time | None = None
    sort_order: int = Field(default=0, index=True)
    is_lunch_break: bool = False
    active: bool = True


class ScheduleDay(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("child_id", "weekday", name="uq_schedule_day_child_weekday"),)

    id: int | None = Field(default=None, primary_key=True)
    child_id: int = Field(foreign_key="child.id", index=True)
    weekday: int = Field(ge=1, le=7, index=True)
    has_school: bool = True
    school_start: time | None = None
    school_end: time | None = None
    is_school_day: bool = True
    notes: str = ""
    valid_from: date | None = None
    valid_to: date | None = None


class ScheduleBlock(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    schedule_day_id: int | None = Field(default=None, foreign_key="scheduleday.id", index=True)
    child_id: int = Field(default=0, foreign_key="child.id", index=True)
    weekday: int = Field(default=1, ge=1, le=7, index=True)
    title: str = Field(default="", index=True)
    starts_at: time
    ends_at: time
    block_type: ScheduleBlockType = Field(default=ScheduleBlockType.school_subject, index=True)
    subject: str | None = None
    location: str | None = None
    room: str = ""
    counts_as_school: bool = True
    requires_sports_gear: bool = False
    affects_timeline: bool = True
    color: str | None = None
    notes: str | None = None
    sort_order: int = Field(default=0, index=True)
    active: bool = True


class RoutineDaySetting(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("child_id", "weekday", name="uq_routine_day_child_weekday"),)

    id: int | None = Field(default=None, primary_key=True)
    child_id: int = Field(foreign_key="child.id", index=True)
    weekday: int = Field(ge=1, le=7, index=True)
    wake_time: time | None = None
    teeth_start: time | None = None
    leave_countdown_minutes: int = Field(default=10, ge=1, le=60)
    timer_hooks: str = Field(default="10,3")
    notes: str = ""
    active: bool = True
    updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))


class TaskTemplate(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("child_id", "slug", name="uq_task_template_child_slug"),)

    id: int | None = Field(default=None, primary_key=True)
    child_id: int | None = Field(default=None, foreign_key="child.id", index=True)
    slug: str = Field(index=True)
    title: str
    description: str = ""
    kind: TaskKind = Field(default=TaskKind.required, index=True)
    coin_value: int = 0
    penalty_value: int = 0
    active: bool = True
    applies_weekdays: str = "0,1,2,3,4"
    sort_order: int = Field(default=0, index=True)


class TaskInstance(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("task_template_id", "child_id", "task_date", name="uq_task_instance_template_child_date"),)

    id: int | None = Field(default=None, primary_key=True)
    task_template_id: int | None = Field(default=None, foreign_key="tasktemplate.id", index=True)
    child_id: int = Field(foreign_key="child.id", index=True)
    task_date: date = Field(index=True)
    title: str
    kind: TaskKind = Field(default=TaskKind.required, index=True)
    coin_value: int = 0
    penalty_value: int = 0
    status: TaskStatus = Field(default=TaskStatus.open, index=True)
    completed_at: datetime | None = None
    source: str = "template"


class TaskCompletion(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("task_instance_id", "idempotency_key", name="uq_task_completion_idempotency"),)

    id: int | None = Field(default=None, primary_key=True)
    task_instance_id: int = Field(foreign_key="taskinstance.id", index=True)
    child_id: int = Field(foreign_key="child.id", index=True)
    completed_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    undone_at: datetime | None = None
    idempotency_key: str = Field(index=True)


class CoinTransaction(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("idempotency_key", name="uq_coin_transaction_idempotency"),)

    id: int | None = Field(default=None, primary_key=True)
    child_id: int = Field(foreign_key="child.id", index=True)
    transaction_type: CoinTransactionType = Field(index=True)
    amount: int
    reserved_amount: int = 0
    comment: str = ""
    task_instance_id: int | None = Field(default=None, foreign_key="taskinstance.id")
    benefit_redemption_id: int | None = Field(default=None, foreign_key="benefitredemption.id")
    reference_transaction_id: int | None = Field(default=None, foreign_key="cointransaction.id", index=True)
    idempotency_key: str = Field(index=True)
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc), index=True)


class Benefit(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("slug", name="uq_benefit_slug"),)

    id: int | None = Field(default=None, primary_key=True)
    slug: str = Field(index=True)
    title: str
    description: str = ""
    price: int = Field(ge=0)
    child_id: int | None = Field(default=None, foreign_key="child.id", index=True)
    active: bool = True
    sort_order: int = Field(default=0, index=True)


class BenefitRedemption(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("benefit_id", "child_id", "idempotency_key", name="uq_benefit_redemption_idempotency"),)

    id: int | None = Field(default=None, primary_key=True)
    benefit_id: int = Field(foreign_key="benefit.id", index=True)
    child_id: int = Field(foreign_key="child.id", index=True)
    status: BenefitRedemptionStatus = Field(default=BenefitRedemptionStatus.reserved, index=True)
    price: int = Field(ge=0)
    requested_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    fulfilled_at: datetime | None = None
    cancelled_at: datetime | None = None
    idempotency_key: str = Field(index=True)


class ScreenTimeDayAllowance(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("child_id", "day", name="uq_screen_time_day_child_day"),)

    id: int | None = Field(default=None, primary_key=True)
    child_id: int = Field(foreign_key="child.id", index=True)
    day: date = Field(index=True)
    base_minutes: int = Field(default=40, ge=0)
    bonus_minutes: int = Field(default=0, ge=0)
    adjustment_minutes: int = Field(default=0)
    locked: bool = False
    notes: str = ""
    updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))


class ScreenTimeSession(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    child_id: int = Field(foreign_key="child.id", index=True)
    day: date = Field(index=True)
    started_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc), index=True)
    ended_at: datetime | None = Field(default=None, index=True)
    duration_seconds: int = Field(default=0, ge=0)
    status: ScreenTimeSessionStatus = Field(default=ScreenTimeSessionStatus.active, index=True)
    idempotency_key: str = Field(default="", index=True)


class ScreenTimeEarnRule(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("slug", name="uq_screen_time_earn_rule_slug"),)

    id: int | None = Field(default=None, primary_key=True)
    slug: str = Field(index=True)
    title: str
    description: str = ""
    required_minutes: int = Field(default=20, ge=1)
    reward_minutes: int = Field(default=10, ge=1)
    coin_reward: int = Field(default=1, ge=0)
    child_id: int | None = Field(default=None, foreign_key="child.id", index=True)
    active: bool = True
    sort_order: int = Field(default=0, index=True)


class ScreenTimeEarnCompletion(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("rule_id", "child_id", "day", "idempotency_key", name="uq_screen_time_earn_completion_key"),)

    id: int | None = Field(default=None, primary_key=True)
    rule_id: int = Field(foreign_key="screentimeearnrule.id", index=True)
    child_id: int = Field(foreign_key="child.id", index=True)
    day: date = Field(index=True)
    reward_minutes: int = Field(default=0, ge=0)
    coin_reward: int = Field(default=0, ge=0)
    coin_transaction_id: int | None = Field(default=None, foreign_key="cointransaction.id", index=True)
    completed_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    idempotency_key: str = Field(index=True)


class Exam(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    child_id: int = Field(foreign_key="child.id", index=True)
    subject: str
    topic: str
    exam_date: date = Field(index=True)
    study_sessions: int = Field(default=3, ge=0)
    include_weekends: bool = False
    status: ExamStatus = Field(default=ExamStatus.planned, index=True)


class StudyPlanItem(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("exam_id", "child_id", "study_date", name="uq_study_plan_exam_child_date"),)

    id: int | None = Field(default=None, primary_key=True)
    exam_id: int = Field(foreign_key="exam.id", index=True)
    child_id: int = Field(foreign_key="child.id", index=True)
    study_date: date = Field(index=True)
    subject: str
    topic: str
    task_instance_id: int | None = Field(default=None, foreign_key="taskinstance.id")
    cancelled: bool = False


class SpecialDay(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("child_id", "day", "day_type", name="uq_special_day_child_day_type"),)

    id: int | None = Field(default=None, primary_key=True)
    child_id: int | None = Field(default=None, foreign_key="child.id", index=True)
    day: date = Field(index=True)
    day_type: SpecialDayType = Field(index=True)
    title: str
    notes: str = ""


class CalendarEvent(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("day", "title", "child_id", name="uq_calendar_event_day_title_child"),)

    id: int | None = Field(default=None, primary_key=True)
    day: date = Field(index=True)
    title: str = Field(index=True)
    description: str = ""
    child_id: int | None = Field(default=None, foreign_key="child.id", index=True)
    icon: str | None = None
    color: str | None = None
    active: bool = True
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))


class ChallengePlayer(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("name", name="uq_challenge_player_name"),)

    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    avatar: str = "🧠"
    color: str = "#64748b"
    is_child: bool = False
    linked_child_id: int | None = Field(default=None, foreign_key="child.id", index=True)
    active: bool = True
    sort_order: int = Field(default=0, index=True)


class GeoChallengeTarget(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("name", name="uq_geo_challenge_target_name"),)

    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    target_type: str = Field(default="city", index=True)
    latitude: float
    longitude: float
    x_percent: float = Field(default=50.0, ge=0, le=100)
    y_percent: float = Field(default=50.0, ge=0, le=100)
    canton: str | None = None
    difficulty: int = Field(default=1, ge=1, le=5, index=True)
    info_text: str = ""
    active: bool = True
    sort_order: int = Field(default=0, index=True)


class GeoChallengeAttempt(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    player_id: int = Field(foreign_key="challengeplayer.id", index=True)
    target_id: int = Field(foreign_key="geochallengetarget.id", index=True)
    day: date = Field(index=True)
    selected_latitude: float | None = None
    selected_longitude: float | None = None
    selected_x_percent: float = Field(default=50.0, ge=0, le=100)
    selected_y_percent: float = Field(default=50.0, ge=0, le=100)
    distance_km: float
    # Backward-compatible raw score column from the first MVP. New code treats
    # raw_points as scoring output and credited_points as leaderboard/conversion truth.
    points: float
    raw_points: float = Field(default=0.0)
    credited_points: float = Field(default=0.0)
    daily_cap_reached: bool = Field(default=False)
    awarded_band: str = Field(index=True)
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc), index=True)


class ChallengeWeeklyConversion(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("player_id", "week_start", name="uq_challenge_weekly_conversion_player_week"),)

    id: int | None = Field(default=None, primary_key=True)
    player_id: int = Field(foreign_key="challengeplayer.id", index=True)
    child_id: int | None = Field(default=None, foreign_key="child.id", index=True)
    week_start: date = Field(index=True)
    challenge_points: float
    coins_awarded: int
    coin_transaction_id: int | None = Field(default=None, foreign_key="cointransaction.id")
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc), index=True)


class DailyPhrase(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    language: str = Field(index=True)
    level: str = Field(default="beginner", index=True)
    foreign_text: str = Field(index=True)
    german_translation: str
    wrong_option_1: str
    wrong_option_2: str
    theme: str | None = None
    active: bool = True
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))


class DailyPhraseAssignment(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("child_id", "day", "slot", name="uq_daily_phrase_assignment_child_day_slot"),)

    id: int | None = Field(default=None, primary_key=True)
    child_id: int = Field(foreign_key="child.id", index=True)
    day: date = Field(index=True)
    slot: str = Field(default="06:00", index=True)
    phrase_id: int = Field(foreign_key="dailyphrase.id", index=True)
    language: str = Field(index=True)
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))


class PhraseQuizAttempt(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("child_id", "day", "slot", "phrase_id", name="uq_phrase_quiz_attempt_child_day_slot_phrase"),)

    id: int | None = Field(default=None, primary_key=True)
    child_id: int = Field(foreign_key="child.id", index=True)
    day: date = Field(index=True)
    slot: str = Field(default="06:00", index=True)
    phrase_id: int = Field(foreign_key="dailyphrase.id", index=True)
    selected_answer: str
    is_correct: bool
    answered_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))


class DailyPhraseSlotAssignment(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("child_id", "day", "slot", name="uq_daily_phrase_slot_assignment_child_day_slot"),)

    id: int | None = Field(default=None, primary_key=True)
    child_id: int = Field(foreign_key="child.id", index=True)
    day: date = Field(index=True)
    slot: str = Field(default="06:00", index=True)
    phrase_id: int = Field(foreign_key="dailyphrase.id", index=True)
    language: str = Field(index=True)
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))


class PhraseQuizSlotAttempt(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("child_id", "day", "slot", "phrase_id", name="uq_phrase_quiz_slot_attempt_child_day_slot_phrase"),)

    id: int | None = Field(default=None, primary_key=True)
    child_id: int = Field(foreign_key="child.id", index=True)
    day: date = Field(index=True)
    slot: str = Field(default="06:00", index=True)
    phrase_id: int = Field(foreign_key="dailyphrase.id", index=True)
    selected_answer: str
    is_correct: bool
    answered_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))


class DailyMathTask(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    child_id: int | None = Field(default=None, foreign_key="child.id", index=True)
    level: int = Field(default=1, ge=1, le=5, index=True)
    prompt: str
    correct_answer: str
    wrong_option_1: str
    wrong_option_2: str
    explanation: str | None = None
    active: bool = True
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))


class DailyMathAssignment(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("child_id", "day", name="uq_daily_math_assignment_child_day"),)

    id: int | None = Field(default=None, primary_key=True)
    child_id: int = Field(foreign_key="child.id", index=True)
    day: date = Field(index=True)
    task_id: int | None = Field(default=None, foreign_key="dailymathtask.id", index=True)
    level_snapshot: int = Field(default=1)
    prompt: str
    correct_answer: str
    wrong_option_1: str
    wrong_option_2: str
    explanation: str | None = None
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))


class DailyMathAttempt(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("child_id", "day", "task_id", name="uq_daily_math_attempt_child_day_task"),)

    id: int | None = Field(default=None, primary_key=True)
    child_id: int = Field(foreign_key="child.id", index=True)
    day: date = Field(index=True)
    task_id: int | None = Field(default=None, foreign_key="dailymathtask.id", index=True)
    selected_answer: str
    is_correct: bool
    answered_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))


class DailyKnowledgeQuestion(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    child_id: int | None = Field(default=None, foreign_key="child.id", index=True)
    level: int = Field(default=1, ge=1, le=4, index=True)
    category: str = Field(index=True)
    question: str
    correct_answer: str
    wrong_option_1: str
    wrong_option_2: str
    explanation: str
    active: bool = True
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))


class DailyKnowledgeAssignment(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("child_id", "day", name="uq_daily_knowledge_assignment_child_day"),)

    id: int | None = Field(default=None, primary_key=True)
    child_id: int = Field(foreign_key="child.id", index=True)
    day: date = Field(index=True)
    question_id: int = Field(foreign_key="dailyknowledgequestion.id", index=True)
    level_snapshot: int = Field(default=1)
    category: str = Field(index=True)
    question: str
    correct_answer: str
    wrong_option_1: str
    wrong_option_2: str
    explanation: str
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))


class DailyKnowledgeAttempt(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("child_id", "day", "question_id", name="uq_daily_knowledge_attempt_child_day_question"),)

    id: int | None = Field(default=None, primary_key=True)
    child_id: int = Field(foreign_key="child.id", index=True)
    day: date = Field(index=True)
    question_id: int = Field(foreign_key="dailyknowledgequestion.id", index=True)
    selected_answer: str
    is_correct: bool
    answered_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))


class AppSetting(SQLModel, table=True):
    key: str = Field(primary_key=True)
    value: str
    description: str = ""
    updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))


class AdminCredential(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("username", name="uq_admin_credential_username"),)

    id: int | None = Field(default=None, primary_key=True)
    username: str = Field(index=True)
    pin_hash: str
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))


class AdminSession(SQLModel, table=True):
    __table_args__ = (UniqueConstraint("token_hash", name="uq_admin_session_token_hash"),)

    id: int | None = Field(default=None, primary_key=True)
    token_hash: str = Field(index=True)
    username: str = Field(default="admin", index=True)
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    expires_at: datetime = Field(index=True)
    revoked_at: datetime | None = None
