import base64
import hashlib
import logging
import os
from datetime import date, time
from pathlib import Path

from sqlmodel import Session, SQLModel, create_engine, select
from sqlalchemy import func, text

from app.core.config import get_settings
from app.models.child import Child
from app.models.domain import (
    AdminCredential,
    AppSetting,
    Benefit,
    Exam,
    DailyPhrase,
    DailyMathAssignment,
    DailyMathAttempt,
    DailyMathTask,
    DailyKnowledgeQuestion,
    GeoChallengeAttempt,
    GeoChallengeTarget,
    CalendarEvent,
    ChallengePlayer,
    ChallengeWeeklyConversion,
    ChildSettings,
    RoutineDaySetting,
    ScheduleBlock,
    ScheduleBlockType,
    ScheduleDay,
    ScheduleTimeSlot,
    ScreenTimeDayAllowance,
    ScreenTimeEarnCompletion,
    ScreenTimeEarnRule,
    ScreenTimeSession,
    TaskKind,
    TaskInstance,
    TaskStatus,
    TaskTemplate,
)

settings = get_settings()
logger = logging.getLogger("familydashboard.persistence")

sqlite_path = settings.sqlite_path
if sqlite_path is not None:
    sqlite_path.parent.mkdir(parents=True, exist_ok=True)

connect_args = {"check_same_thread": False} if settings.database_url.startswith("sqlite") else {}
engine = create_engine(settings.database_url, echo=False, connect_args=connect_args)


def hash_pin(pin: str, salt: bytes | None = None) -> str:
    salt = salt or os.urandom(16)
    digest = hashlib.pbkdf2_hmac("sha256", pin.encode("utf-8"), salt, 240_000)
    return "pbkdf2_sha256$240000$%s$%s" % (
        base64.b64encode(salt).decode("ascii"),
        base64.b64encode(digest).decode("ascii"),
    )


def init_db() -> None:
    import app.models.child  # noqa: F401 - ensure model registration
    import app.models.domain  # noqa: F401 - ensure model registration

    SQLModel.metadata.create_all(engine)
    migrate_sqlite_schema()
    assert_persistent_database_not_empty_if_initialized()
    log_database_state("before_seed")
    seed_database()
    log_database_state("after_seed")


def _count_rows(session: Session, model) -> int:
    return int(session.exec(select(func.count(model.id))).one() or 0)


def database_health_counts(session: Session) -> dict[str, int]:
    return {
        "child": _count_rows(session, Child),
        "scheduleblock": _count_rows(session, ScheduleBlock),
        "tasktemplate": _count_rows(session, TaskTemplate),
        "benefit": _count_rows(session, Benefit),
        "exam": _count_rows(session, Exam),
        "calendarevent": _count_rows(session, CalendarEvent),
        "geochallengetarget": _count_rows(session, GeoChallengeTarget),
        "screentimeallowance": _count_rows(session, ScreenTimeDayAllowance),
        "screentimeearnrule": _count_rows(session, ScreenTimeEarnRule),
        "screentimeearncompletion": _count_rows(session, ScreenTimeEarnCompletion),
        "screentimesession": _count_rows(session, ScreenTimeSession),
        "routine_day_setting": _count_rows(session, RoutineDaySetting),
    }


def log_database_state(stage: str) -> None:
    db_path = str(sqlite_path) if sqlite_path is not None else settings.database_url
    db_size = sqlite_path.stat().st_size if sqlite_path is not None and sqlite_path.exists() else None
    with Session(engine) as session:
        counts = database_health_counts(session)
    level = logger.error if counts["scheduleblock"] == 0 or counts["tasktemplate"] == 0 else logger.info
    level("db_startup stage=%s path=%s size=%s counts=%s", stage, db_path, db_size, counts)


def assert_persistent_database_not_empty_if_initialized() -> None:
    with Session(engine) as session:
        marker = session.get(AppSetting, "production_db_initialized")
        counts = database_health_counts(session)
    if marker and (counts["scheduleblock"] == 0 or counts["tasktemplate"] == 0):
        db_path = str(sqlite_path) if sqlite_path is not None else settings.database_url
        message = (
            "Persistenzfehler: Produktiv-DB wirkt leer. Backup prüfen. "
            f"path={db_path} counts={counts}"
        )
        logger.error(message)
        raise RuntimeError(message)


def migrate_sqlite_schema() -> None:
    if not settings.database_url.startswith("sqlite"):
        return
    with engine.begin() as conn:
        tables = {row[0] for row in conn.execute(text("SELECT name FROM sqlite_master WHERE type='table'"))}
        if "childsettings" in tables:
            columns = {row[1] for row in conn.execute(text("PRAGMA table_info(childsettings)"))}
            for column, ddl in {
                "language_quiz_language": "ALTER TABLE childsettings ADD COLUMN language_quiz_language VARCHAR NOT NULL DEFAULT 'en'",
                "language_quiz_level": "ALTER TABLE childsettings ADD COLUMN language_quiz_level INTEGER NOT NULL DEFAULT 2",
                "math_quiz_level": "ALTER TABLE childsettings ADD COLUMN math_quiz_level INTEGER NOT NULL DEFAULT 2",
                "knowledge_quiz_level": "ALTER TABLE childsettings ADD COLUMN knowledge_quiz_level INTEGER NOT NULL DEFAULT 2",
            }.items():
                if column not in columns:
                    conn.execute(text(ddl))
        if "scheduleday" in tables:
            columns = {row[1] for row in conn.execute(text("PRAGMA table_info(scheduleday)"))}
            for column, ddl in {
                "has_school": "ALTER TABLE scheduleday ADD COLUMN has_school BOOLEAN NOT NULL DEFAULT 1",
                "valid_from": "ALTER TABLE scheduleday ADD COLUMN valid_from DATE",
                "valid_to": "ALTER TABLE scheduleday ADD COLUMN valid_to DATE",
            }.items():
                if column not in columns:
                    conn.execute(text(ddl))
        if "routinedaysetting" in tables:
            columns = {row[1] for row in conn.execute(text("PRAGMA table_info(routinedaysetting)"))}
            for column, ddl in {
                "wake_time": "ALTER TABLE routinedaysetting ADD COLUMN wake_time TIME",
                "teeth_start": "ALTER TABLE routinedaysetting ADD COLUMN teeth_start TIME",
                "leave_countdown_minutes": "ALTER TABLE routinedaysetting ADD COLUMN leave_countdown_minutes INTEGER NOT NULL DEFAULT 10",
                "timer_hooks": "ALTER TABLE routinedaysetting ADD COLUMN timer_hooks VARCHAR NOT NULL DEFAULT '10,3'",
                "notes": "ALTER TABLE routinedaysetting ADD COLUMN notes VARCHAR NOT NULL DEFAULT ''",
                "active": "ALTER TABLE routinedaysetting ADD COLUMN active BOOLEAN NOT NULL DEFAULT 1",
                "updated_at": "ALTER TABLE routinedaysetting ADD COLUMN updated_at DATETIME",
            }.items():
                if column not in columns:
                    conn.execute(text(ddl))
        if "scheduleblock" in tables:
            columns = {row[1] for row in conn.execute(text("PRAGMA table_info(scheduleblock)"))}
            for column, ddl in {
                "child_id": "ALTER TABLE scheduleblock ADD COLUMN child_id INTEGER NOT NULL DEFAULT 0",
                "weekday": "ALTER TABLE scheduleblock ADD COLUMN weekday INTEGER NOT NULL DEFAULT 1",
                "title": "ALTER TABLE scheduleblock ADD COLUMN title VARCHAR NOT NULL DEFAULT ''",
                "block_type": "ALTER TABLE scheduleblock ADD COLUMN block_type VARCHAR NOT NULL DEFAULT 'school_subject'",
                "location": "ALTER TABLE scheduleblock ADD COLUMN location VARCHAR",
                "counts_as_school": "ALTER TABLE scheduleblock ADD COLUMN counts_as_school BOOLEAN NOT NULL DEFAULT 1",
                "affects_timeline": "ALTER TABLE scheduleblock ADD COLUMN affects_timeline BOOLEAN NOT NULL DEFAULT 1",
                "color": "ALTER TABLE scheduleblock ADD COLUMN color VARCHAR",
                "notes": "ALTER TABLE scheduleblock ADD COLUMN notes VARCHAR",
                "active": "ALTER TABLE scheduleblock ADD COLUMN active BOOLEAN NOT NULL DEFAULT 1",
            }.items():
                if column not in columns:
                    conn.execute(text(ddl))
        if "cointransaction" in tables:
            columns = {row[1] for row in conn.execute(text("PRAGMA table_info(cointransaction)"))}
            if "reference_transaction_id" not in columns:
                conn.execute(text("ALTER TABLE cointransaction ADD COLUMN reference_transaction_id INTEGER"))
        if "screentimeearnrule" in tables:
            columns = {row[1] for row in conn.execute(text("PRAGMA table_info(screentimeearnrule)"))}
            if "coin_reward" not in columns:
                conn.execute(text("ALTER TABLE screentimeearnrule ADD COLUMN coin_reward INTEGER NOT NULL DEFAULT 1"))
                conn.execute(text("UPDATE screentimeearnrule SET coin_reward = 1 WHERE slug IN ('buch-lesen-20', 'sport-20') AND coin_reward = 0"))
        if "screentimeearncompletion" in tables:
            columns = {row[1] for row in conn.execute(text("PRAGMA table_info(screentimeearncompletion)"))}
            if "coin_reward" not in columns:
                conn.execute(text("ALTER TABLE screentimeearncompletion ADD COLUMN coin_reward INTEGER NOT NULL DEFAULT 0"))
            if "coin_transaction_id" not in columns:
                conn.execute(text("ALTER TABLE screentimeearncompletion ADD COLUMN coin_transaction_id INTEGER"))
        for table in ("dailymathassignment", "dailymathtask"):
            if table in tables:
                for denom in (2, 3, 4, 6, 8):
                    prefix = f"1/{denom} von "
                    conn.execute(text(
                        f"UPDATE {table} "
                        "SET prompt = substr(prompt, :start, length(prompt) - :trim) || :suffix "
                        "WHERE prompt LIKE :pattern"
                    ), {"start": len(prefix) + 1, "trim": len(prefix) + 4, "suffix": f" : {denom} = ?", "pattern": f"{prefix}% = ?"})
        if "geochallengetarget" in tables:
            columns = {row[1] for row in conn.execute(text("PRAGMA table_info(geochallengetarget)"))}
            for column, ddl in {
                "x_percent": "ALTER TABLE geochallengetarget ADD COLUMN x_percent FLOAT NOT NULL DEFAULT 50.0",
                "y_percent": "ALTER TABLE geochallengetarget ADD COLUMN y_percent FLOAT NOT NULL DEFAULT 50.0",
            }.items():
                if column not in columns:
                    conn.execute(text(ddl))
        if "geochallengeattempt" in tables:
            columns = {row[1] for row in conn.execute(text("PRAGMA table_info(geochallengeattempt)"))}
            added_columns: set[str] = set()
            for column, ddl in {
                "selected_x_percent": "ALTER TABLE geochallengeattempt ADD COLUMN selected_x_percent FLOAT NOT NULL DEFAULT 50.0",
                "selected_y_percent": "ALTER TABLE geochallengeattempt ADD COLUMN selected_y_percent FLOAT NOT NULL DEFAULT 50.0",
                "raw_points": "ALTER TABLE geochallengeattempt ADD COLUMN raw_points FLOAT NOT NULL DEFAULT 0.0",
                "credited_points": "ALTER TABLE geochallengeattempt ADD COLUMN credited_points FLOAT NOT NULL DEFAULT 0.0",
                "daily_cap_reached": "ALTER TABLE geochallengeattempt ADD COLUMN daily_cap_reached BOOLEAN NOT NULL DEFAULT 0",
            }.items():
                if column not in columns:
                    conn.execute(text(ddl))
                    added_columns.add(column)
            if added_columns & {"raw_points", "credited_points"}:
                # Preserve historical leaderboard/conversion semantics for existing rows.
                conn.execute(text("UPDATE geochallengeattempt SET raw_points = points WHERE raw_points = 0.0"))
                conn.execute(text("UPDATE geochallengeattempt SET credited_points = points WHERE credited_points = 0.0"))
        for table in ("dailyphraseassignment", "phrasequizattempt"):
            if table in tables:
                columns = {row[1] for row in conn.execute(text(f"PRAGMA table_info({table})"))}
                if "slot" not in columns:
                    conn.execute(text(f"ALTER TABLE {table} ADD COLUMN slot VARCHAR NOT NULL DEFAULT '06:00'"))


def seed_database() -> None:
    with Session(engine) as session:
        children = seed_children(session)
        seed_child_settings(session, children)
        seed_task_templates(session, children)
        deactivate_timeline_replaced_task_templates(session)
        seed_benefits(session)
        seed_schedule(session, children)
        seed_calendar_events(session, children)
        seed_challenge_players(session, children)
        seed_geo_challenge_targets(session)
        seed_screen_time_earn_rules(session)
        seed_reference_exams(session, children)
        seed_daily_phrases(session)
        seed_daily_knowledge_questions(session)
        seed_app_settings(session)
        seed_admin_credential(session)
        session.commit()


def seed_children(session: Session) -> dict[str, Child]:
    seed = [
        {"name": "Emilia", "color": "#E879A8", "avatar": "🌸", "sort_order": 1},
        {"name": "Valerie", "color": "#22B8A0", "avatar": "🦋", "sort_order": 2},
    ]
    result: dict[str, Child] = {}
    for item in seed:
        child = session.exec(select(Child).where(Child.name == item["name"])).first()
        if child is None:
            child = Child(**item)
            session.add(child)
            session.flush()
        result[child.name] = child
    return result


def seed_child_settings(session: Session, children: dict[str, Child]) -> None:
    defaults = {
        "Emilia": {"language_quiz_language": "fr", "language_quiz_level": 1, "math_quiz_level": 3, "knowledge_quiz_level": 3},
        "Valerie": {"language_quiz_language": "en", "language_quiz_level": 2, "math_quiz_level": 2, "knowledge_quiz_level": 2},
    }
    for child in children.values():
        exists = session.exec(select(ChildSettings).where(ChildSettings.child_id == child.id)).first()
        child_defaults = defaults.get(child.name, {"language_quiz_language": "en", "language_quiz_level": 2, "math_quiz_level": 2})
        if exists is None:
            session.add(ChildSettings(child_id=child.id, default_wake_time=time(6, 45), default_school_start=time(8, 15), commute_minutes=15, **child_defaults))
        else:
            changed = False
            if exists.commute_minutes == 12:
                exists.commute_minutes = 15
                changed = True
            # Existing rows migrated with generic defaults get the child-specific initial levels once.
            if child.name in defaults and exists.language_quiz_language == "en" and child.name == "Emilia":
                exists.language_quiz_language = "fr"
                exists.language_quiz_level = 1
                exists.math_quiz_level = 3
                exists.knowledge_quiz_level = 3
                changed = True
            if child.name == "Valerie" and exists.knowledge_quiz_level == 3:
                exists.knowledge_quiz_level = 2
                changed = True
            if changed:
                session.add(exists)


def seed_task_templates(session: Session, children: dict[str, Child]) -> None:
    templates = [
        (None, "schultasche-check", "Schultasche bereitstellen", TaskKind.required, 0, -1, 20),
        (None, "musikinstrument-ueben", "Musikinstrument üben", TaskKind.required, 0, -1, 30),
        (None, "wuschi-fuettern", "Wuschi füttern", TaskKind.bonus, 2, 0, 100),
        (None, "zimmer-10-min", "Zimmer 10 Minuten aufräumen", TaskKind.bonus, 3, 0, 110),
    ]
    for child in children.values():
        for _, slug, title, kind, coin_value, penalty_value, sort_order in templates:
            tombstone = session.get(AppSetting, f"deleted_task_template:{child.id}:{slug}")
            if tombstone is not None:
                continue
            exists = session.exec(
                select(TaskTemplate).where(TaskTemplate.child_id == child.id, TaskTemplate.slug == slug)
            ).first()
            if exists is None:
                session.add(
                    TaskTemplate(
                        child_id=child.id,
                        slug=slug,
                        title=title,
                        kind=kind,
                        coin_value=coin_value,
                        penalty_value=penalty_value,
                        sort_order=sort_order,
                    )
                )


def is_toothbrushing_template(template: TaskTemplate) -> bool:
    text = f"{template.slug} {template.title}".lower()
    return any(part in text for part in ("zaehne", "zähne", "zahnputzen", "zähneputzen"))


def deactivate_timeline_replaced_task_templates(session: Session) -> None:
    today = date.today()
    for template in session.exec(select(TaskTemplate)).all():
        if not is_toothbrushing_template(template):
            continue
        template.active = False
        session.add(template)
        instances = session.exec(
            select(TaskInstance).where(
                TaskInstance.task_template_id == template.id,
                TaskInstance.status == TaskStatus.open,
                TaskInstance.task_date >= today,
            )
        ).all()
        for instance in instances:
            instance.status = TaskStatus.cancelled
            session.add(instance)


def seed_benefits(session: Session) -> None:
    benefits = [
        {"slug": "filmabend", "title": "Filmabend aussuchen", "description": "Ein Kind wählt den Familienfilm.", "price": 12, "sort_order": 10},
        {"slug": "dessert-wunsch", "title": "Dessert-Wunsch", "description": "Dessert für einen Abend aussuchen.", "price": 8, "sort_order": 20},
        {"slug": "extra-vorlesen", "title": "Extra Vorlesen", "description": "15 Minuten zusätzliche Vorlesezeit.", "price": 6, "sort_order": 30},
    ]
    for item in benefits:
        exists = session.exec(select(Benefit).where(Benefit.slug == item["slug"])).first()
        if exists is None:
            session.add(Benefit(**item))


def seed_screen_time_earn_rules(session: Session) -> None:
    rules = [
        {"slug": "buch-lesen-20", "title": "20 Minuten Buch lesen", "description": "Lies ruhig 20 Minuten in einem Buch.", "required_minutes": 20, "reward_minutes": 10, "coin_reward": 1, "sort_order": 10},
        {"slug": "sport-20", "title": "20 Minuten Sport", "description": "Bewege dich 20 Minuten draussen, im Verein oder zuhause.", "required_minutes": 20, "reward_minutes": 10, "coin_reward": 1, "sort_order": 20},
    ]
    for item in rules:
        exists = session.exec(select(ScreenTimeEarnRule).where(ScreenTimeEarnRule.slug == item["slug"])).first()
        if exists is None:
            session.add(ScreenTimeEarnRule(**item))
        elif exists.coin_reward == 0:
            exists.coin_reward = item["coin_reward"]
            session.add(exists)


def seed_schedule(session: Session, children: dict[str, Child]) -> None:
    seed_time_slots(session)
    seed_schedule_days(session, children)
    seed_schedule_blocks(session, children)


def seed_time_slots(session: Session) -> None:
    slots = [
        ("07:30–08:15", time(7, 30), time(8, 15), 10, False),
        ("08:20–09:05", time(8, 20), time(9, 5), 20, False),
        ("09:10–09:55", time(9, 10), time(9, 55), 30, False),
        ("10:15–11:00", time(10, 15), time(11, 0), 40, False),
        ("11:05–11:50", time(11, 5), time(11, 50), 50, False),
        ("Mittagspause", None, None, 60, True),
        ("13:30–14:15", time(13, 30), time(14, 15), 70, False),
        ("14:20–15:05", time(14, 20), time(15, 5), 80, False),
        ("15:20–16:05", time(15, 20), time(16, 5), 90, False),
        ("16:10–16:55", time(16, 10), time(16, 55), 100, False),
        ("17:00–17:45", time(17, 0), time(17, 45), 110, False),
        ("18:30–19:30", time(18, 30), time(19, 30), 120, False),
    ]
    for label, start, end, sort_order, lunch in slots:
        exists = session.exec(select(ScheduleTimeSlot).where(ScheduleTimeSlot.label == label)).first()
        if exists is None:
            session.add(ScheduleTimeSlot(label=label, start_time=start, end_time=end, sort_order=sort_order, is_lunch_break=lunch))


def seed_schedule_days(session: Session, children: dict[str, Child]) -> None:
    for child in children.values():
        for weekday in range(1, 6):
            day = session.exec(
                select(ScheduleDay).where(ScheduleDay.child_id == child.id, ScheduleDay.weekday == weekday)
            ).first()
            if day is None:
                day = ScheduleDay(child_id=child.id, weekday=weekday, school_start=time(8, 20), school_end=time(15, 5), has_school=True, is_school_day=True)
                session.add(day)
                session.flush()


def reference_schedule_blocks() -> list[tuple[str, int, str, time, time, str, bool, bool, str]]:
    """Manual reference timetable from the photo provided by the family.

    No OCR is used at runtime. Blank coloured school cells are stored as "Schule" so
    the dashboard can still calculate school start/end from structured data.
    """
    return [
        # Montag
        ("Emilia", 1, "Schule", time(8, 20), time(11, 5), "school_subject", True, False, ""),
        ("Emilia", 1, "Englisch", time(11, 5), time(11, 50), "school_subject", True, False, ""),
        ("Emilia", 1, "Turnen", time(13, 30), time(15, 5), "sports", True, True, "Turnhalle"),
        ("Emilia", 1, "Mädchenriege", time(17, 30), time(19, 0), "sports", False, True, "Turnhalle"),
        ("Valerie", 1, "Schule", time(8, 20), time(11, 50), "school_subject", True, False, ""),
        ("Valerie", 1, "Englisch", time(13, 30), time(15, 5), "school_subject", True, False, ""),
        ("Valerie", 1, "Mädchenriege", time(17, 30), time(19, 0), "sports", False, True, "Turnhalle"),
        # Dienstag
        ("Emilia", 2, "Schule", time(8, 20), time(10, 15), "school_subject", True, False, ""),
        ("Emilia", 2, "Turnen", time(10, 15), time(11, 0), "sports", True, True, "Turnhalle"),
        ("Emilia", 2, "Schule", time(11, 5), time(11, 50), "school_subject", True, False, ""),
        ("Emilia", 2, "Gitarre ZI 205", time(12, 50), time(13, 15), "music", False, False, "ZI 205"),
        ("Valerie", 2, "Schule", time(8, 20), time(11, 50), "school_subject", True, False, ""),
        ("Valerie", 2, "Turnen", time(13, 30), time(15, 5), "sports", True, True, "Turnhalle"),
        ("Valerie", 2, "Klavier EG", time(15, 10), time(15, 35), "music", False, False, "EG"),
        # Mittwoch
        ("Emilia", 3, "Schule", time(7, 30), time(10, 15), "school_subject", True, False, ""),
        ("Emilia", 3, "TTG", time(10, 15), time(11, 50), "school_subject", True, False, "Werkraum"),
        ("Valerie", 3, "Schule", time(8, 20), time(9, 10), "school_subject", True, False, ""),
        ("Valerie", 3, "Turnen", time(9, 10), time(9, 55), "sports", True, True, "Turnhalle"),
        ("Valerie", 3, "Schule", time(10, 15), time(11, 50), "school_subject", True, False, ""),
        # Donnerstag
        ("Emilia", 4, "Englisch", time(8, 20), time(9, 55), "school_subject", True, False, ""),
        ("Emilia", 4, "Schule", time(10, 15), time(11, 50), "school_subject", True, False, ""),
        ("Emilia", 4, "Musik", time(13, 30), time(15, 5), "school_subject", True, False, ""),
        ("Valerie", 4, "Englisch", time(7, 30), time(8, 15), "school_subject", True, False, ""),
        ("Valerie", 4, "Schule", time(8, 20), time(11, 50), "school_subject", True, False, ""),
        ("Valerie", 4, "TTG", time(13, 30), time(15, 5), "school_subject", True, False, "Werkraum"),
        # Freitag
        ("Emilia", 5, "Schule", time(8, 20), time(11, 50), "school_subject", True, False, ""),
        ("Emilia", 5, "Halbklasse", time(13, 30), time(15, 5), "school_subject", True, False, ""),
        ("Valerie", 5, "Schule", time(8, 20), time(11, 50), "school_subject", True, False, ""),
    ]


def seed_schedule_blocks(session: Session, children: dict[str, Child], *, reset_to_reference: bool = False) -> None:
    colors = {"Emilia": "#E879A8", "Valerie": "#22B8A0"}
    block_seed = reference_schedule_blocks()
    if reset_to_reference:
        for row in session.exec(select(ScheduleBlock)).all():
            row.active = False
            session.add(row)
        session.flush()
    for child_name, weekday, title, start, end, block_type, counts_as_school, sports, location in block_seed:
        child = children[child_name]
        day = session.exec(select(ScheduleDay).where(ScheduleDay.child_id == child.id, ScheduleDay.weekday == weekday)).first()
        exists = None
        if not reset_to_reference:
            exists = session.exec(
                select(ScheduleBlock).where(
                    ScheduleBlock.child_id == child.id,
                    ScheduleBlock.weekday == weekday,
                    ScheduleBlock.title == title,
                    ScheduleBlock.starts_at == start,
                    ScheduleBlock.active == True,  # noqa: E712
                )
            ).first()
        if exists is not None:
            continue
        row = ScheduleBlock(schedule_day_id=day.id if day else None, child_id=child.id, weekday=weekday, starts_at=start, ends_at=end)
        row.schedule_day_id = day.id if day else None
        row.title = title
        row.ends_at = end
        row.block_type = ScheduleBlockType(block_type)
        row.subject = title
        row.location = location
        row.room = location
        row.counts_as_school = counts_as_school
        row.requires_sports_gear = sports or title.lower() == "turnen" or block_type == "sports"
        row.affects_timeline = True
        row.color = colors[child_name]
        row.notes = "Referenz Stundenplan IMG_4167"
        row.sort_order = weekday * 100 + start.hour * 4 + start.minute // 15
        row.active = True
        session.add(row)



def seed_calendar_events(session: Session, children: dict[str, Child]) -> None:
    emilia = children.get("Emilia")
    events = [
        ("2026-06-16", "Probe Amphitheater/Aula (?)", "Turnschuhe, Trinken, Kappe, Sonnencreme", None, "🎭"),
        ("2026-06-16", "Jimmy Carr", "Whiteboard-Eintrag", None, "🎤"),
        ("2026-06-17", "Probe Amphitheater", "Whiteboard-Eintrag", None, "🎭"),
        ("2026-06-18", "Schwimmen E", "Whiteboard-Eintrag", emilia.id if emilia else None, "🏊"),
        ("2026-06-19", "Jokertag", "Whiteboard-Eintrag", None, "⭐"),
        ("2026-06-19", "Europapark", "Whiteboard-Eintrag", None, "🎢"),
        ("2026-06-20", "Europapark", "Whiteboard-Eintrag", None, "🎢"),
        ("2026-06-21", "Marcel/Rocky (?)", "Unsicherer Whiteboard-Eintrag", None, "❔"),
        ("2026-06-21", "Agility (?)", "Unsicherer Whiteboard-Eintrag", None, "🐕"),
        ("2026-06-22", "DH Marcel", "Whiteboard-Eintrag", None, "📌"),
        ("2026-06-22", "Mädchenriege", "Informativ; beeinflusst Aufgaben/Timeline nicht", None, "🤸"),
        ("2026-06-23", "Bibliothek", "Whiteboard-Eintrag", None, "📚"),
        ("2026-06-23", "Instr. (?)", "Unsicherer Whiteboard-Eintrag", None, "❔"),
        ("2026-06-24", "Bei Rahel", "Whiteboard-Eintrag", None, "🏡"),
        ("2026-06-26", "Jugendfest", "Whiteboard-Eintrag", None, "🎉"),
        ("2026-06-27", "Jugendfest", "Whiteboard-Eintrag", None, "🎉"),
        ("2026-06-29", "Schulfrei", "Whiteboard-Eintrag", None, "🏖️"),
        ("2026-06-29", "Mädchenriege", "Informativ; beeinflusst Aufgaben/Timeline nicht", None, "🤸"),
        ("2026-07-02", "Mutti (?)", "Unsicherer Whiteboard-Eintrag", None, "❔"),
        ("2026-07-03", "FDP Bräteln", "Whiteboard-Eintrag", None, "🔥"),
        ("2026-07-04", "Fionna zügeln", "Whiteboard-Eintrag", None, "📦"),
        ("2026-07-06", "Sommerferien", "Whiteboard-Eintrag", None, "☀️"),
        ("2026-07-07", "Sommerferien", "Whiteboard-Eintrag", None, "☀️"),
        ("2026-07-08", "Sommerferien", "Whiteboard-Eintrag", None, "☀️"),
        ("2026-07-09", "Sommerferien", "Whiteboard-Eintrag", None, "☀️"),
        ("2026-07-10", "Sommerferien", "Whiteboard-Eintrag", None, "☀️"),
        ("2026-07-11", "Sommerferien", "Whiteboard-Eintrag", None, "☀️"),
        ("2026-07-12", "Sommerferien", "Whiteboard-Eintrag", None, "☀️"),
    ]
    for day_text, title, description, child_id, icon in events:
        day = __import__('datetime').date.fromisoformat(day_text)
        exists = session.exec(select(CalendarEvent).where(CalendarEvent.day == day, CalendarEvent.title == title, CalendarEvent.child_id == child_id)).first()
        if exists is None:
            session.add(CalendarEvent(day=day, title=title, description=description, child_id=child_id, icon=icon, active=True))



def seed_reference_exams(session: Session, children: dict[str, Child]) -> None:
    emilia = children.get("Emilia")
    if emilia is None:
        return
    from datetime import date as _date
    from app.models.domain import ExamStatus
    from app.services.calendar import generate_study_plan

    exam_date = _date(2026, 6, 26)
    exists = session.exec(
        select(Exam).where(
            Exam.child_id == emilia.id,
            Exam.subject == "Mathe",
            Exam.exam_date == exam_date,
            Exam.status != ExamStatus.cancelled,
        )
    ).first()
    if exists is None:
        exists = Exam(child_id=emilia.id, subject="Mathe", topic="Division", exam_date=exam_date, study_sessions=3, include_weekends=False)
        session.add(exists)
        session.flush()
    generate_study_plan(session, exists)

def seed_daily_phrases(session: Session) -> None:
    en = [
        ("I am ready for school.", "Ich bin bereit für die Schule.", "Ich bin müde nach der Schule.", "Ich suche meine Schuhe.", "school"),
        ("Can I have some water, please?", "Kann ich bitte etwas Wasser haben?", "Kann ich bitte mein Buch haben?", "Ich habe heute Sport.", "politeness"),
        ("I like reading books.", "Ich lese gern Bücher.", "Ich schreibe gern Briefe.", "Ich mag keine Bücher.", "hobby"),
        ("Today is a good day.", "Heute ist ein guter Tag.", "Morgen ist mein Geburtstag.", "Heute ist sehr kalt.", "daily"),
        ("I need my sports bag.", "Ich brauche meine Sporttasche.", "Ich brauche meine Zahnbürste.", "Ich habe meine Tasche gefunden.", "school"),
        ("Where is my pencil?", "Wo ist mein Bleistift?", "Wo ist mein Mittagessen?", "Ich habe einen Bleistift.", "school"),
        ("I can help you.", "Ich kann dir helfen.", "Du kannst mir helfen.", "Ich kann dich hören.", "family"),
        ("Please wait for me.", "Bitte warte auf mich.", "Bitte ruf mich an.", "Bitte lies mir vor.", "politeness"),
        ("I am hungry.", "Ich habe Hunger.", "Ich bin durstig.", "Ich bin fertig.", "food"),
        ("This is my sister.", "Das ist meine Schwester.", "Das ist meine Freundin.", "Das ist mein Zimmer.", "family"),
        ("I brush my teeth.", "Ich putze meine Zähne.", "Ich wasche meine Hände.", "Ich ziehe meine Schuhe an.", "routine"),
        ("My homework is finished.", "Meine Hausaufgaben sind fertig.", "Meine Tasche ist leer.", "Meine Jacke ist blau.", "school"),
        ("I have music today.", "Ich habe heute Musik.", "Ich habe heute frei.", "Ich habe morgen Musik.", "school"),
        ("The book is on the table.", "Das Buch liegt auf dem Tisch.", "Die Tasche steht unter dem Tisch.", "Das Buch ist in der Schule.", "objects"),
        ("I go home at four o'clock.", "Ich gehe um vier Uhr nach Hause.", "Ich gehe um fünf Uhr schlafen.", "Ich gehe um vier Uhr zur Schule.", "time"),
        ("Can you repeat that, please?", "Kannst du das bitte wiederholen?", "Kannst du bitte schneller gehen?", "Kannst du mir Wasser bringen?", "school"),
        ("I don't understand.", "Ich verstehe nicht.", "Ich höre nicht zu.", "Ich bin nicht zu Hause.", "school"),
        ("See you tomorrow.", "Bis morgen.", "Bis heute Abend.", "Guten Morgen.", "politeness"),
        ("I am proud of you.", "Ich bin stolz auf dich.", "Ich bin wütend auf dich.", "Ich warte auf dich.", "family"),
        ("It is time for dinner.", "Es ist Zeit fürs Abendessen.", "Es ist Zeit fürs Frühstück.", "Es ist Zeit für die Schule.", "food"),
        ("I need a break.", "Ich brauche eine Pause.", "Ich brauche eine Jacke.", "Ich mache eine Aufgabe.", "feelings"),
        ("My favourite colour is blue.", "Meine Lieblingsfarbe ist blau.", "Mein Lieblingsessen ist Reis.", "Meine Jacke ist blau.", "personal"),
        ("I play the piano.", "Ich spiele Klavier.", "Ich spiele Fussball.", "Ich höre Musik.", "music"),
        ("The weather is sunny.", "Das Wetter ist sonnig.", "Das Wasser ist kalt.", "Der Weg ist lang.", "weather"),
        ("I am going to bed.", "Ich gehe ins Bett.", "Ich gehe in die Küche.", "Ich gehe nach draussen.", "bedtime"),
        ("Good night and sleep well.", "Gute Nacht und schlaf gut.", "Guten Morgen und viel Spass.", "Guten Appetit und danke.", "bedtime"),
        ("I packed my school bag.", "Ich habe meine Schultasche gepackt.", "Ich habe mein Zimmer aufgeräumt.", "Ich habe meine Schuhe geputzt.", "school"),
        ("I would like an apple.", "Ich möchte einen Apfel.", "Ich möchte ein Buch.", "Ich esse einen Apfel nicht.", "food"),
        ("That was fun.", "Das hat Spass gemacht.", "Das war sehr schwer.", "Das ist morgen.", "feelings"),
        ("I am learning English.", "Ich lerne Englisch.", "Ich lerne Französisch.", "Ich spreche Deutsch.", "school"),
    ]
    fr = [
        ("Bonjour, je m'appelle Emilia.", "Hallo, ich heisse Emilia.", "Guten Abend, ich bin müde.", "Hallo, ich suche Emilia.", "intro"),
        ("J'ai onze ans.", "Ich bin elf Jahre alt.", "Ich habe elf Bücher.", "Ich bin neun Jahre alt.", "intro"),
        ("Je vais à l'école.", "Ich gehe zur Schule.", "Ich gehe nach Hause.", "Ich bleibe im Bett.", "school"),
        ("J'aime lire.", "Ich lese gern.", "Ich schreibe gern.", "Ich schlafe gern.", "hobby"),
        ("Merci beaucoup.", "Vielen Dank.", "Guten Morgen.", "Bitte warte.", "politeness"),
        ("Où est mon sac?", "Wo ist meine Tasche?", "Wo ist mein Buch?", "Das ist meine Tasche.", "school"),
        ("Je suis prête.", "Ich bin bereit.", "Ich bin müde.", "Ich bin hungrig.", "daily"),
        ("Je voudrais de l'eau.", "Ich möchte Wasser.", "Ich möchte Brot.", "Ich trinke Milch.", "food"),
        ("C'est ma sœur.", "Das ist meine Schwester.", "Das ist meine Mutter.", "Das ist mein Zimmer.", "family"),
        ("Je ne comprends pas.", "Ich verstehe nicht.", "Ich höre nicht.", "Ich weiss es.", "school"),
        ("Répète, s'il te plaît.", "Wiederhole es bitte.", "Warte bitte.", "Lies bitte.", "school"),
        ("J'ai faim.", "Ich habe Hunger.", "Ich habe Durst.", "Ich habe Zeit.", "food"),
        ("J'ai soif.", "Ich habe Durst.", "Ich habe Hunger.", "Ich habe Angst.", "food"),
        ("Il est temps de dormir.", "Es ist Zeit zu schlafen.", "Es ist Zeit zu essen.", "Es ist Zeit zu lernen.", "bedtime"),
        ("Bonne nuit.", "Gute Nacht.", "Guten Tag.", "Bis morgen.", "bedtime"),
        ("À demain.", "Bis morgen.", "Bis heute Abend.", "Guten Appetit.", "politeness"),
        ("Je range ma chambre.", "Ich räume mein Zimmer auf.", "Ich putze meine Zähne.", "Ich packe meine Tasche.", "routine"),
        ("Je brosse mes dents.", "Ich putze meine Zähne.", "Ich wasche meine Hände.", "Ich kämme meine Haare.", "routine"),
        ("Mon livre est ici.", "Mein Buch ist hier.", "Meine Tasche ist dort.", "Mein Heft ist blau.", "objects"),
        ("J'aime la musique.", "Ich mag Musik.", "Ich mache Musik.", "Ich höre nicht zu.", "music"),
        ("Je joue de la guitare.", "Ich spiele Gitarre.", "Ich spiele Klavier.", "Ich singe ein Lied.", "music"),
        ("Aujourd'hui, il fait beau.", "Heute ist schönes Wetter.", "Heute ist Schule.", "Heute ist es spät.", "weather"),
        ("Je suis contente.", "Ich bin glücklich.", "Ich bin bereit.", "Ich bin zu Hause.", "feelings"),
        ("C'est facile.", "Das ist einfach.", "Das ist schwer.", "Das ist fertig.", "school"),
        ("C'est difficile.", "Das ist schwierig.", "Das ist lustig.", "Das ist klein.", "school"),
        ("Je prends mon sac.", "Ich nehme meine Tasche.", "Ich suche meine Schuhe.", "Ich packe mein Essen.", "school"),
        ("Je mange une pomme.", "Ich esse einen Apfel.", "Ich trinke Wasser.", "Ich möchte einen Apfel.", "food"),
        ("Ma couleur préférée est rose.", "Meine Lieblingsfarbe ist rosa.", "Mein Lieblingsessen ist Reis.", "Meine Jacke ist rosa.", "personal"),
        ("Je parle un peu français.", "Ich spreche ein bisschen Französisch.", "Ich spreche sehr gut Englisch.", "Ich lerne ein bisschen Mathe.", "school"),
        ("Bravo, c'est super!", "Bravo, das ist super!", "Guten Abend, schlaf gut!", "Bitte komm schnell!", "encouragement"),
    ]
    for language, rows in (("en", en), ("fr", fr)):
        for foreign_text, german, wrong1, wrong2, theme in rows:
            exists = session.exec(select(DailyPhrase).where(DailyPhrase.language == language, DailyPhrase.foreign_text == foreign_text)).first()
            if exists is None:
                session.add(DailyPhrase(language=language, level="beginner", foreign_text=foreign_text, german_translation=german, wrong_option_1=wrong1, wrong_option_2=wrong2, theme=theme, active=True))


def seed_daily_knowledge_questions(session: Session) -> None:
    rows = [
        # Level 1
        (1, 'Natur & Tiere', 'Was braucht eine Pflanze zum Wachsen?', 'Licht und Wasser', 'Schokolade', 'Fernbedienung', 'Pflanzen brauchen Licht, Wasser, Luft und Erde.'),
        (1, 'Zeit', 'Wie viele Tage hat eine normale Woche?', '7', '5', '10', 'Eine Woche hat sieben Tage.'),
        (1, 'Schweiz', 'Welche Farbe hat das Schweizer Kreuz?', 'Weiss', 'Grün', 'Blau', 'Das Schweizer Kreuz ist weiss auf rotem Grund.'),
        (1, 'Körper & Gesundheit', 'Was hilft nach dem Spielen besonders gut gegen Durst?', 'Wasser trinken', 'Noch mehr rennen', 'Salz essen', 'Wasser ist ein guter Durstlöscher.'),
        (1, 'Freundlichkeit', 'Was sagst du, wenn dir jemand hilft?', 'Danke', 'Geh weg', 'Nie wieder', 'Danke sagen ist freundlich.'),
        (1, 'Medien', 'Solltest du fremden Personen im Internet deine Adresse schicken?', 'Nein', 'Ja, immer', 'Nur wenn sie nett schreiben', 'Persönliche Daten bleiben privat.'),
        (1, 'Umwelt', 'Wohin gehört Papier meistens?', 'Ins Altpapier', 'In den See', 'In den Backofen', 'Papier kann oft recycelt werden.'),
        (1, 'Geld', 'Was ist ein Sparschwein?', 'Ein Ort zum Geld sparen', 'Ein Tier im Zoo', 'Ein Schulbus', 'Darin kann man Münzen sammeln.'),
        (1, 'Kalender', 'Was kommt nach Montag?', 'Dienstag', 'Sonntag', 'Freitag', 'Nach Montag kommt Dienstag.'),
        (1, 'Logik', 'Was passt nicht: Apfel, Birne, Zug?', 'Zug', 'Apfel', 'Birne', 'Apfel und Birne sind Früchte.'),
        # Level 2
        (2, 'Schweiz', 'Welcher See liegt bei Zürich?', 'Zürichsee', 'Bodensee', 'Thunersee', 'Zürich liegt am Zürichsee.'),
        (2, 'Geld', 'Was bedeutet es, Geld zu sparen?', 'Nicht alles sofort ausgeben', 'Alles sofort kaufen', 'Geld verlieren', 'Sparen heisst, etwas für später behalten.'),
        (2, 'Natur & Tiere', 'Warum sind Bienen wichtig?', 'Sie bestäuben viele Pflanzen', 'Sie machen Schnee', 'Sie fressen Plastik', 'Viele Früchte entstehen erst nach Bestäubung.'),
        (2, 'Schweiz', 'In welchem Kanton liegen Brugg und Windisch?', 'Aargau', 'Tessin', 'Wallis', 'Brugg und Windisch liegen im Kanton Aargau.'),
        (2, 'Zeit', 'Wie viele Monate hat ein Jahr?', '12', '10', '14', 'Ein Jahr hat zwölf Monate.'),
        (2, 'Medien', 'Warum sollte man bei Internet-Informationen nachdenken?', 'Weil nicht alles stimmen muss', 'Weil Lesen verboten ist', 'Weil Bilder immer lügen', 'Im Internet kann auch Falsches stehen.'),
        (2, 'Umwelt', 'Warum löscht man Licht in leeren Zimmern?', 'Um Energie zu sparen', 'Damit Pflanzen schlafen', 'Damit der Boden sauber bleibt', 'Unnötiges Licht verbraucht Strom.'),
        (2, 'Körper & Gesundheit', 'Warum wäscht man vor dem Essen die Hände?', 'Damit weniger Keime ans Essen kommen', 'Damit die Hände blau werden', 'Damit man schneller isst', 'Händewaschen entfernt Schmutz und viele Keime.'),
        (2, 'Freundlichkeit', 'Was ist in einem Streit oft hilfreich?', 'Ruhig sagen, was man fühlt', 'Sofort schreien', 'Einfach wegrennen und nie reden', 'Ruhige Worte helfen, Lösungen zu finden.'),
        (2, 'Geschichte', 'Was war Vindonissa?', 'Ein römisches Legionslager', 'Ein moderner Flughafen', 'Ein See im Tessin', 'Vindonissa lag beim heutigen Windisch.'),
        (2, 'Deutsch', 'Was hilft beim Verstehen eines Textes?', 'Wichtige Stellen nochmals lesen', 'Nur die Überschrift anschauen', 'Das Buch schliessen', 'Nochmal lesen hilft beim Verstehen.'),
        (2, 'Mathe-Anwendung', 'Wenn du Preise vergleichst, worauf achtest du?', 'Was ich für mein Geld bekomme', 'Nur auf die schönste Farbe', 'Nie auf den Preis', 'Vergleichen hilft bei guten Entscheidungen.'),
        (2, 'Natur & Tiere', 'Was machen Regenwürmer im Boden?', 'Sie lockern den Boden', 'Sie bauen Computer', 'Sie trinken Benzin', 'Regenwürmer helfen dem Boden.'),
        (2, 'Schweiz', 'Wie heisst die Bundesstadt der Schweiz?', 'Bern', 'Zürich', 'Lugano', 'Bern ist die Bundesstadt.'),
        (2, 'Zeit', 'Was zeigt der Minutenzeiger?', 'Die Minuten', 'Den Monat', 'Das Wetter', 'Der lange Zeiger zeigt oft die Minuten.'),
        (2, 'Umwelt', 'Was ist Recycling?', 'Aus alten Dingen wieder Material nutzen', 'Alles verbrennen', 'Dinge im Wald verstecken', 'Recycling spart Rohstoffe.'),
        (2, 'Medien', 'Was ist ein sicheres Passwort eher?', 'Ein langes geheimes Passwort', '1234', 'Der eigene Vorname', 'Lange, geheime Passwörter sind sicherer.'),
        (2, 'Geld', 'Was ist ein Wunsch im Vergleich zu einem Bedürfnis?', 'Etwas Schönes, aber nicht immer nötig', 'Immer lebenswichtig', 'Ein anderes Wort für Hausaufgaben', 'Bedürfnisse sind wichtiger als Wünsche.'),
        (2, 'Natur', 'Warum fällt ein Stein schneller zu Boden als eine Feder im Zimmer?', 'Die Feder wird stärker von Luft gebremst', 'Der Stein kennt den Weg', 'Die Feder schläft', 'Luft bremst leichte Dinge stark.'),
        (2, 'Logik', 'Alle Katzen sind Tiere. Minka ist eine Katze. Was ist Minka?', 'Ein Tier', 'Eine Pflanze', 'Ein Kanton', 'Wenn Minka eine Katze ist, ist sie ein Tier.'),
        # Level 3
        (3, 'Schweiz', 'Wie heisst die Hauptstadt des Kantons Aargau?', 'Aarau', 'Brugg', 'Baden', 'Aarau ist die Hauptstadt des Kantons Aargau.'),
        (3, 'Geld', 'Was ist ein Budget?', 'Ein Plan für Einnahmen und Ausgaben', 'Eine Einkaufstasche', 'Ein Schulfach', 'Ein Budget hilft, Geld einzuteilen.'),
        (3, 'Medien', 'Warum sollte man Quellen im Internet prüfen?', 'Weil nicht alles im Internet stimmt', 'Weil das Internet sonst langsamer wird', 'Weil Bilder keine Texte haben', 'Quellenprüfung schützt vor falschen Informationen.'),
        (3, 'Schweiz', 'Welche drei Orte gehören zur Region Wasserschloss der Schweiz?', 'Aare, Reuss und Limmat treffen sich dort', 'Drei Berge berühren sich', 'Drei Flughäfen liegen nebeneinander', 'Beim Wasserschloss treffen grosse Flüsse zusammen.'),
        (3, 'Natur', 'Was ist Photosynthese ganz einfach gesagt?', 'Pflanzen machen mit Licht Nahrung', 'Steine wachsen im Wasser', 'Tiere bauen Häuser', 'Pflanzen nutzen Licht, Wasser und Luft.'),
        (3, 'Körper', 'Warum ist Schlaf für Kinder wichtig?', 'Der Körper erholt sich und das Gehirn lernt', 'Man verpasst dann jede Mahlzeit', 'Schlaf macht Hausaufgaben fertig', 'Im Schlaf verarbeitet das Gehirn den Tag.'),
        (3, 'Zeit', 'Was ist ein Schaltjahr?', 'Ein Jahr mit einem zusätzlichen Tag im Februar', 'Ein Jahr ohne Sommer', 'Ein Jahr mit 13 Wochen', 'Schaltjahre gleichen den Kalender aus.'),
        (3, 'Umwelt', 'Warum sind erneuerbare Energien nützlich?', 'Sie können sich natürlich erneuern', 'Sie verbrauchen immer mehr Kohle', 'Sie funktionieren nur im Weltall', 'Sonne, Wind und Wasser können laufend Energie liefern.'),
        (3, 'Deutsch', 'Was ist die Hauptaussage eines Textes?', 'Die wichtigste Botschaft', 'Das längste Wort', 'Die Seitenzahl', 'Die Hauptaussage fasst das Wichtigste zusammen.'),
        (3, 'Logik', 'Wenn alle Quadrate Rechtecke sind, ist jedes Rechteck ein Quadrat?', 'Nein', 'Ja', 'Nur am Montag', 'Ein Quadrat ist spezieller als ein Rechteck.'),
        (3, 'Geschichte', 'Warum bauten Römer Strassen?', 'Für Handel, Reisen und Soldaten', 'Damit Kühe tanzen', 'Nur als Spielplatz', 'Strassen verbanden Orte im Römischen Reich.'),
        (3, 'Freundlichkeit', 'Was ist ein guter Kompromiss?', 'Beide Seiten geben etwas nach', 'Eine Person gewinnt alles', 'Niemand hört zu', 'Kompromisse suchen eine faire Mitte.'),
        (3, 'Geld', 'Warum lohnt sich Preisvergleich?', 'Man kann bewusster entscheiden', 'Man muss immer das Teuerste nehmen', 'Preise sind immer gleich', 'Vergleichen spart manchmal Geld.'),
        (3, 'Medien', 'Was bedeutet Werbung?', 'Sie will dich von etwas überzeugen', 'Sie ist immer Schulstoff', 'Sie ist nie geplant', 'Werbung soll Aufmerksamkeit und Kaufwünsche wecken.'),
        (3, 'Natur', 'Warum schwimmt Holz oft auf Wasser?', 'Es ist meist weniger dicht als Wasser', 'Es hat einen Motor', 'Es ist magnetisch', 'Dichte beeinflusst, ob etwas schwimmt.'),
        (3, 'Schweiz', 'Welche Sprache hört man im Tessin besonders oft?', 'Italienisch', 'Rätoromanisch', 'Englisch', 'Im Tessin ist Italienisch Amtssprache.'),
        (3, 'Kalender', 'Was bedeutet Quartal?', 'Ein Viertel eines Jahres', 'Ein halber Tag', 'Eine Schulnote', 'Ein Jahr hat vier Quartale.'),
        (3, 'Körper', 'Welche Aufgabe hat das Herz?', 'Blut durch den Körper pumpen', 'Gedanken speichern', 'Essen zerkleinern', 'Das Herz ist eine Pumpe.'),
        (3, 'Umwelt', 'Warum sollte man Batterien nicht in den normalen Müll werfen?', 'Sie enthalten Stoffe, die speziell entsorgt werden müssen', 'Sie sind essbar', 'Sie werden sonst zu Papier', 'Batterien gehören in Sammelstellen.'),
        (3, 'Logik', 'Du hast drei Wege. Zwei sind gesperrt. Wie viele Wege bleiben offen?', '1', '2', '3', 'Von drei Wegen bleibt nach zwei gesperrten einer übrig.'),
        # Level 4
        (4, 'Schweiz', 'Was bedeutet direkte Demokratie in der Schweiz vereinfacht?', 'Bürgerinnen und Bürger können über Vorlagen abstimmen', 'Nur Könige entscheiden', 'Kinder müssen Gesetze schreiben', 'In der Schweiz gibt es Volksabstimmungen.'),
        (4, 'Medien', 'Was ist der Unterschied zwischen Meinung und Fakt?', 'Ein Fakt ist überprüfbar, eine Meinung ist eine persönliche Ansicht', 'Beides ist immer gleich', 'Eine Meinung ist immer eine Zahl', 'Fakten kann man prüfen.'),
        (4, 'Natur', 'Warum sind Wälder für das Klima wichtig?', 'Bäume speichern Kohlenstoff und kühlen die Umgebung', 'Bäume heizen die Luft absichtlich', 'Wälder machen Metall', 'Wälder beeinflussen Luft, Wasser und Temperatur.'),
        (4, 'Geld', 'Was bedeutet Zins beim Sparen ganz einfach?', 'Zusätzliches Geld für geliehenes oder gespartes Geld', 'Eine Sportart', 'Ein Kalenderblatt', 'Zins ist ein Preis für Geld über Zeit.'),
        (4, 'Körper', 'Welche Aufgabe haben die Lungen?', 'Sauerstoff aufnehmen und Kohlendioxid abgeben', 'Blut zählen', 'Knochen bauen', 'Die Lungen helfen beim Atmen.'),
        (4, 'Geschichte', 'Warum sind historische Quellen wichtig?', 'Sie zeigen, woher wir Wissen über früher haben', 'Sie ersetzen jedes Museum', 'Sie sind immer erfunden', 'Quellen helfen, Vergangenheit zu verstehen.'),
        (4, 'Logik', 'Wenn eine Aussage durch ein Gegenbeispiel widerlegt wird, was heisst das?', 'Sie gilt nicht immer', 'Sie ist dadurch stärker', 'Sie wird zu Musik', 'Ein Gegenbeispiel zeigt eine Ausnahme.'),
        (4, 'Umwelt', 'Was ist der Wasserkreislauf?', 'Wasser verdunstet, bildet Wolken und fällt als Niederschlag', 'Wasser verschwindet für immer', 'Wasser wird zu Stein', 'Wasser bewegt sich in einem Kreislauf.'),
        (4, 'Deutsch', 'Was ist eine Schlussfolgerung?', 'Etwas, das man aus Hinweisen ableitet', 'Der erste Buchstabe', 'Ein Satzzeichen', 'Man kombiniert Hinweise zu einer Erkenntnis.'),
        (4, 'Schweiz', 'Warum haben Kantone eigene Regeln?', 'Die Schweiz ist föderal aufgebaut', 'Kantone sind private Firmen', 'Regeln sind überall verboten', 'Föderalismus verteilt Aufgaben auf Ebenen.'),
    ]
    for level, category, question, correct, wrong1, wrong2, explanation in rows:
        exists = session.exec(select(DailyKnowledgeQuestion).where(DailyKnowledgeQuestion.question == question)).first()
        if exists is None:
            session.add(DailyKnowledgeQuestion(level=level, category=category, question=question, correct_answer=correct, wrong_option_1=wrong1, wrong_option_2=wrong2, explanation=explanation, active=True))


def seed_app_settings(session: Session) -> None:
    settings_seed = {
        "timezone": ("Europe/Zurich", "Lokale Zeitzone für Tagesabschluss und Kalender."),
        "coin_name": ("Gasser-Coins", "Anzeigename der Familienwährung."),
        "initial_admin_pin_set": ("true", "Initiale Admin-PIN wurde gehasht gesetzt."),
        "production_db_initialized": ("true", "Schutzmarker: Produktivdatenbank wurde initialisiert; zentrale Tabellen dürfen beim Start nicht leer sein."),
        "departure_warning_sound_enabled": ("true", "Warnton drei Minuten vor Loslaufen aktivieren."),
        "language_quiz_coin_value": ("1", "Coins pro korrekt gelöster Sprach-Challenge."),
        "math_quiz_coin_value": ("1", "Coins pro korrekt gelöster Rechnung des Tages."),
        "knowledge_quiz_coin_value": ("1", "Coins pro korrekt gelöster Allgemeinwissen-Frage."),
        "screen_time_default_daily_minutes": ("40", "Standard-Pensum Bildschirmzeit pro Kind und Tag."),
        "screen_time_exchange_coin_cost": ("4", "Shop: Coin-Kosten für zusätzliche Bildschirmzeit."),
        "screen_time_exchange_minutes": ("10", "Shop: zusätzliche Minuten pro Bildschirmzeit-Tausch."),
        "screen_time_alarm_sound": ("classic", "Alarmton bei abgelaufener Bildschirmzeit: classic, gentle, arcade."),
    }
    for key, (value, description) in settings_seed.items():
        exists = session.get(AppSetting, key)
        if exists is None:
            session.add(AppSetting(key=key, value=value, description=description))
        elif key == "screen_time_default_daily_minutes" and exists.value == "60":
            exists.value = "40"
            exists.description = description
            session.add(exists)


def seed_admin_credential(session: Session) -> None:
    target_pin = os.getenv("FAMILYDASHBOARD_INITIAL_ADMIN_PIN", "9999")
    target_marker = "admin_pin_seed_2026_06_9999" if target_pin == "9999" else f"admin_pin_seed_env_{hashlib.sha256(target_pin.encode('utf-8')).hexdigest()[:12]}"
    marker = session.get(AppSetting, "admin_pin_marker")
    credential = session.exec(select(AdminCredential).where(AdminCredential.username == "admin")).first()

    if credential is None:
        session.add(AdminCredential(username="admin", pin_hash=hash_pin(target_pin)))
    elif marker is None or marker.value != target_marker:
        credential.pin_hash = hash_pin(target_pin)
        session.add(credential)

    if marker is None:
        session.add(AppSetting(key="admin_pin_marker", value=target_marker, description="Marker für zuletzt gesetzte Admin-PIN; speichert nie die PIN selbst."))
    else:
        marker.value = target_marker
        session.add(marker)


def get_session():
    with Session(engine) as session:
        yield session


def seed_challenge_players(session: Session, children: dict[str, Child]) -> None:
    seed = [
        {"name": "Emilia", "avatar": "🌸", "color": "#E879A8", "is_child": True, "linked_child_id": children["Emilia"].id, "sort_order": 1},
        {"name": "Valerie", "avatar": "🦋", "color": "#22B8A0", "is_child": True, "linked_child_id": children["Valerie"].id, "sort_order": 2},
        {"name": "Marcel", "avatar": "🧔", "color": "#3b82f6", "is_child": False, "linked_child_id": None, "sort_order": 3},
        {"name": "Melanie", "avatar": "👩", "color": "#a855f7", "is_child": False, "linked_child_id": None, "sort_order": 4},
    ]
    for row in seed:
        existing = session.exec(select(ChallengePlayer).where(ChallengePlayer.name == row["name"])).first()
        if existing is None:
            session.add(ChallengePlayer(**row))
        else:
            for key, value in row.items():
                setattr(existing, key, value)
            existing.active = True
            session.add(existing)


def _geo_target_xy(latitude: float, longitude: float) -> tuple[float, float]:
    # Wikimedia Switzerland_adm_location_map.svg location-map bounds.
    left, right, top, bottom = 5.8, 10.7, 47.9, 45.75
    x = ((longitude - left) / (right - left)) * 100
    y = ((top - latitude) / (top - bottom)) * 100
    return max(0, min(100, x)), max(0, min(100, y))


def seed_geo_challenge_targets(session: Session) -> None:
    targets = [
        ("Zürich", "city", 47.3769, 8.5417, "ZH", 1, "Zürich ist die grösste Stadt der Schweiz und liegt am Zürichsee."),
        ("Bern", "city", 46.9480, 7.4474, "BE", 1, "Bern ist die Bundesstadt der Schweiz. Dort steht das Bundeshaus."),
        ("Basel", "city", 47.5596, 7.5886, "BS", 1, "Basel liegt am Rhein und grenzt an Deutschland und Frankreich."),
        ("Luzern", "city", 47.0502, 8.3093, "LU", 1, "Luzern liegt am Vierwaldstättersee und ist bekannt für die Kapellbrücke."),
        ("Genf", "city", 46.2044, 6.1432, "GE", 1, "Genf liegt am Genfersee und ist Sitz vieler internationaler Organisationen."),
        ("Lausanne", "city", 46.5197, 6.6323, "VD", 1, "Lausanne liegt am Genfersee und ist eine wichtige Stadt in der Romandie."),
        ("St. Gallen", "city", 47.4245, 9.3767, "SG", 1, "St. Gallen ist bekannt für seine Stiftsbibliothek."),
        ("Lugano", "city", 46.0037, 8.9511, "TI", 1, "Lugano liegt im Tessin am Luganersee."),
        ("Chur", "city", 46.8508, 9.5320, "GR", 1, "Chur ist die Hauptstadt des Kantons Graubünden."),
        ("Zürichsee", "lake", 47.2550, 8.6800, "ZH/SG/SZ", 1, "Der Zürichsee liegt südöstlich der Stadt Zürich."),
        ("Genfersee", "lake", 46.4500, 6.5500, "VD/GE/VS", 1, "Der Genfersee ist einer der grössten Seen Europas."),
        ("Bodensee", "lake", 47.6500, 9.3500, "TG/SG", 1, "Der Bodensee liegt im Dreiländereck Schweiz, Deutschland und Österreich."),
        ("Vierwaldstättersee", "lake", 47.0000, 8.4000, "LU/UR/SZ/OW/NW", 1, "Der Vierwaldstättersee liegt in der Zentralschweiz und hat viele Arme."),
        ("Aarau", "city", 47.3925, 8.0442, "AG", 2, "Aarau ist die Hauptstadt des Kantons Aargau."),
        ("Baden", "city", 47.4733, 8.3059, "AG", 2, "Baden liegt an der Limmat und ist für Thermalquellen bekannt."),
        ("Brugg", "city", 47.4864, 8.2084, "AG", 2, "Brugg liegt im Kanton Aargau nahe dem Wasserschloss der Schweiz."),
        ("Windisch", "city", 47.4789, 8.2180, "AG", 2, "Windisch liegt im Kanton Aargau bei Brugg. Dort gibt es das Legionärslager Vindonissa."),
        ("Solothurn", "city", 47.2088, 7.5323, "SO", 2, "Solothurn gilt als schöne Barockstadt an der Aare."),
        ("Schaffhausen", "city", 47.6965, 8.6339, "SH", 2, "Schaffhausen liegt am Rhein nahe beim Rheinfall."),
        ("Thun", "city", 46.7580, 7.6280, "BE", 2, "Thun liegt am Thunersee im Berner Oberland."),
        ("Interlaken", "city", 46.6863, 7.8632, "BE", 2, "Interlaken liegt zwischen Thunersee und Brienzersee."),
        ("Biel/Bienne", "city", 47.1368, 7.2468, "BE", 2, "Biel/Bienne ist zweisprachig: Deutsch und Französisch."),
        ("Zug", "city", 47.1662, 8.5155, "ZG", 2, "Zug liegt am Zugersee und ist Hauptstadt des Kantons Zug."),
        ("Neuenburg", "city", 46.9918, 6.9310, "NE", 2, "Neuenburg liegt am Neuenburgersee."),
        ("Murtensee", "lake", 46.9270, 7.0900, "FR/VD", 2, "Der Murtensee liegt im Drei-Seen-Land."),
        ("Walensee", "lake", 47.1200, 9.2300, "SG/GL", 2, "Der Walensee liegt zwischen steilen Bergen."),
        ("Thunersee", "lake", 46.7000, 7.7167, "BE", 2, "Der Thunersee liegt im Berner Oberland."),
        ("Brienzersee", "lake", 46.7260, 7.9880, "BE", 2, "Der Brienzersee ist für seine türkisfarbene Farbe bekannt."),
        ("Lago Maggiore", "lake", 46.1400, 8.7300, "TI", 2, "Der Lago Maggiore liegt im Tessin und in Italien."),
        ("Sion", "city", 46.2331, 7.3606, "VS", 3, "Sion ist die Hauptstadt des Kantons Wallis."),
        ("Bellinzona", "city", 46.1946, 9.0244, "TI", 3, "Bellinzona ist bekannt für seine Burgen."),
        ("Davos", "city", 46.8027, 9.8350, "GR", 3, "Davos liegt hoch in den Bündner Alpen."),
        ("Zermatt", "city", 46.0207, 7.7491, "VS", 3, "Zermatt liegt am Fuss des Matterhorns."),
        ("Grindelwald", "city", 46.6242, 8.0414, "BE", 3, "Grindelwald liegt im Berner Oberland bei Eiger, Mönch und Jungfrau."),
        ("Andermatt", "city", 46.6354, 8.5930, "UR", 3, "Andermatt liegt im Urserental nahe wichtigen Alpenpässen."),
        ("Arosa", "city", 46.7831, 9.6796, "GR", 3, "Arosa ist ein Bergort im Kanton Graubünden."),
        ("Locarno", "city", 46.1699, 8.7943, "TI", 3, "Locarno liegt am Lago Maggiore."),
        ("La Chaux-de-Fonds", "city", 47.1035, 6.8328, "NE", 3, "La Chaux-de-Fonds ist bekannt für Uhrmacherei."),
        ("Säntis", "mountain", 47.2494, 9.3433, "AI/AR/SG", 3, "Der Säntis ist ein markanter Berg in der Ostschweiz."),
        ("Pilatus", "mountain", 46.9797, 8.2544, "LU/OW/NW", 3, "Der Pilatus steht bei Luzern."),
        ("Matterhorn", "mountain", 45.9763, 7.6586, "VS", 3, "Das Matterhorn ist einer der bekanntesten Berge der Welt."),
        ("Gotthardpass", "region", 46.5586, 8.5610, "UR/TI", 3, "Der Gotthardpass verbindet die Nord- und Südschweiz."),
    ]
    active_names = {row[0] for row in targets}
    existing_targets = session.exec(select(GeoChallengeTarget)).all()
    for existing in existing_targets:
        if existing.name not in active_names and existing.name in {"Rheinfall", "Rheinquelle / Tomasee", "Winterthur", "Samedan"}:
            existing.active = False
            session.add(existing)
    for sort_order, (name, target_type, lat, lon, canton, difficulty, info_text) in enumerate(targets, start=1):
        x_percent, y_percent = _geo_target_xy(lat, lon)
        existing = session.exec(select(GeoChallengeTarget).where(GeoChallengeTarget.name == name)).first()
        data = dict(name=name, target_type=target_type, latitude=lat, longitude=lon, x_percent=round(x_percent, 2), y_percent=round(y_percent, 2), canton=canton, difficulty=difficulty, info_text=info_text, active=True, sort_order=sort_order)
        if existing is None:
            session.add(GeoChallengeTarget(**data))
        else:
            for key, value in data.items():
                setattr(existing, key, value)
            session.add(existing)

