from datetime import date, time

from fastapi import APIRouter, Depends, Header, HTTPException, Query
from pydantic import BaseModel, Field, model_validator
from sqlmodel import Session, select

from app.db.session import get_session, seed_schedule_blocks, seed_schedule_days, seed_time_slots
from app.models import Child, ScheduleBlock, ScheduleBlockType, ScheduleDay, ScheduleTimeSlot
from app.services.admin_auth import get_admin_session
from app.services.schedule import (
    build_current_schedule,
    build_schedule_summary,
    detect_overlap,
    serialize_block,
    serialize_time_slot,
    validate_block_times,
)

router = APIRouter(tags=["schedule"])


def require_admin(
    x_admin_session: str | None = Header(default=None, alias="X-Admin-Session"),
    session: Session = Depends(get_session),
) -> None:
    get_admin_session(session, x_admin_session)


class TimeSlotCreate(BaseModel):
    label: str = Field(min_length=1, max_length=80)
    start_time: time | None = None
    end_time: time | None = None
    sort_order: int = 0
    is_lunch_break: bool = False
    active: bool = True

    @model_validator(mode="after")
    def validate_range(self):
        if not self.is_lunch_break and (self.start_time is None or self.end_time is None):
            raise ValueError("Zeitfenster braucht Start und Ende")
        if self.start_time and self.end_time:
            validate_block_times(self.start_time, self.end_time)
        return self


class TimeSlotUpdate(BaseModel):
    label: str | None = Field(default=None, min_length=1, max_length=80)
    start_time: time | None = None
    end_time: time | None = None
    sort_order: int | None = None
    is_lunch_break: bool | None = None
    active: bool | None = None


class ScheduleBlockCreate(BaseModel):
    child_id: int
    weekday: int = Field(ge=1, le=7)
    title: str = Field(min_length=1, max_length=120)
    start_time: time
    end_time: time
    block_type: ScheduleBlockType = ScheduleBlockType.school_subject
    subject: str | None = None
    location: str | None = None
    counts_as_school: bool | None = None
    requires_sports_gear: bool | None = None
    affects_timeline: bool = True
    color: str | None = None
    notes: str | None = None
    sort_order: int = 0
    active: bool = True
    allow_overlap: bool = False

    @model_validator(mode="after")
    def validate_times_and_defaults(self):
        validate_block_times(self.start_time, self.end_time)
        lower_title = self.title.lower()
        if self.counts_as_school is None:
            self.counts_as_school = self.block_type == ScheduleBlockType.school_subject
        if self.requires_sports_gear is None:
            self.requires_sports_gear = self.block_type == ScheduleBlockType.sports or lower_title == "turnen"
        return self




class SeedReferenceConfirm(BaseModel):
    confirm: str


class ScheduleBlockUpdate(BaseModel):
    child_id: int | None = None
    weekday: int | None = Field(default=None, ge=1, le=7)
    title: str | None = Field(default=None, min_length=1, max_length=120)
    start_time: time | None = None
    end_time: time | None = None
    block_type: ScheduleBlockType | None = None
    subject: str | None = None
    location: str | None = None
    counts_as_school: bool | None = None
    requires_sports_gear: bool | None = None
    affects_timeline: bool | None = None
    color: str | None = None
    notes: str | None = None
    sort_order: int | None = None
    active: bool | None = None
    allow_overlap: bool = False


@router.get("/schedule/current")
def get_current_schedule(session: Session = Depends(get_session)) -> dict:
    return build_current_schedule(session)


@router.get("/schedule/summary")
def get_schedule_summary(date_value: date = Query(..., alias="date"), session: Session = Depends(get_session)) -> dict:
    return build_schedule_summary(session, date_value)


@router.get("/admin/schedule", dependencies=[Depends(require_admin)])
def admin_schedule(session: Session = Depends(get_session)) -> dict:
    return build_current_schedule(session)


@router.get("/admin/schedule/time-slots", dependencies=[Depends(require_admin)])
def admin_time_slots(session: Session = Depends(get_session)) -> dict:
    rows = session.exec(select(ScheduleTimeSlot).order_by(ScheduleTimeSlot.sort_order)).all()
    return {"time_slots": [serialize_time_slot(row) for row in rows]}


@router.post("/admin/schedule/time-slots", status_code=201, dependencies=[Depends(require_admin)])
def admin_create_time_slot(payload: TimeSlotCreate, session: Session = Depends(get_session)) -> dict:
    row = ScheduleTimeSlot(**payload.model_dump())
    session.add(row)
    session.commit()
    session.refresh(row)
    return {"time_slot": serialize_time_slot(row)}


@router.patch("/admin/schedule/time-slots/{slot_id}", dependencies=[Depends(require_admin)])
def admin_update_time_slot(slot_id: int, payload: TimeSlotUpdate, session: Session = Depends(get_session)) -> dict:
    row = session.get(ScheduleTimeSlot, slot_id)
    if row is None:
        raise HTTPException(status_code=404, detail={"code": "time_slot_not_found", "message": "Zeitfenster nicht gefunden"})
    for key, value in payload.model_dump(exclude_unset=True).items():
        setattr(row, key, value)
    if row.start_time and row.end_time:
        validate_block_times(row.start_time, row.end_time)
    session.add(row)
    session.commit()
    session.refresh(row)
    return {"time_slot": serialize_time_slot(row)}


def _ensure_day(session: Session, child_id: int, weekday: int) -> ScheduleDay:
    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, has_school=True, is_school_day=True)
        session.add(day)
        session.flush()
    return day


def _apply_block_payload(row: ScheduleBlock, data: dict) -> None:
    if "start_time" in data:
        row.starts_at = data.pop("start_time")
    if "end_time" in data:
        row.ends_at = data.pop("end_time")
    data.pop("allow_overlap", None)
    for key, value in data.items():
        setattr(row, key, value)
    if row.block_type == ScheduleBlockType.sports or row.title.lower() == "turnen":
        row.requires_sports_gear = True
    row.subject = row.subject or row.title
    row.room = row.location or row.room or ""


@router.post("/admin/schedule/blocks", status_code=201, dependencies=[Depends(require_admin)])
def admin_create_schedule_block(payload: ScheduleBlockCreate, session: Session = Depends(get_session)) -> dict:
    child = session.get(Child, payload.child_id)
    if child is None:
        raise HTTPException(status_code=404, detail={"code": "child_not_found", "message": "Kind nicht gefunden"})
    overlaps = detect_overlap(session, payload.child_id, payload.weekday, payload.start_time, payload.end_time)
    if overlaps and not payload.allow_overlap:
        return {"warning": "overlap", "overlaps": [serialize_block(block, child) for block in overlaps]}
    day = _ensure_day(session, payload.child_id, payload.weekday)
    row = ScheduleBlock(
        schedule_day_id=day.id,
        child_id=payload.child_id,
        weekday=payload.weekday,
        title=payload.title,
        starts_at=payload.start_time,
        ends_at=payload.end_time,
        block_type=payload.block_type,
        subject=payload.subject or payload.title,
        location=payload.location,
        room=payload.location or "",
        counts_as_school=bool(payload.counts_as_school),
        requires_sports_gear=bool(payload.requires_sports_gear),
        affects_timeline=payload.affects_timeline,
        color=payload.color or child.color,
        notes=payload.notes,
        sort_order=payload.sort_order,
        active=payload.active,
    )
    if row.block_type == ScheduleBlockType.sports or row.title.lower() == "turnen":
        row.requires_sports_gear = True
    session.add(row)
    session.commit()
    session.refresh(row)
    return {"schedule_block": serialize_block(row, child)}


@router.patch("/admin/schedule/blocks/{block_id}", dependencies=[Depends(require_admin)])
def admin_update_schedule_block(block_id: int, payload: ScheduleBlockUpdate, session: Session = Depends(get_session)) -> dict:
    row = session.get(ScheduleBlock, block_id)
    if row is None:
        raise HTTPException(status_code=404, detail={"code": "schedule_block_not_found", "message": "Stundenplanblock nicht gefunden"})
    data = payload.model_dump(exclude_unset=True)
    next_start = data.get("start_time", row.starts_at)
    next_end = data.get("end_time", row.ends_at)
    next_child = data.get("child_id", row.child_id)
    next_weekday = data.get("weekday", row.weekday)
    validate_block_times(next_start, next_end)
    overlaps = detect_overlap(session, next_child, next_weekday, next_start, next_end, ignore_block_id=block_id)
    if overlaps and not payload.allow_overlap:
        child = session.get(Child, next_child)
        return {"warning": "overlap", "overlaps": [serialize_block(block, child) for block in overlaps]}
    _apply_block_payload(row, data)
    row.schedule_day_id = _ensure_day(session, row.child_id, row.weekday).id
    session.add(row)
    session.commit()
    session.refresh(row)
    return {"schedule_block": serialize_block(row, session.get(Child, row.child_id))}


@router.delete("/admin/schedule/blocks/{block_id}", dependencies=[Depends(require_admin)])
def admin_delete_schedule_block(block_id: int, session: Session = Depends(get_session)) -> dict:
    row = session.get(ScheduleBlock, block_id)
    if row is None:
        raise HTTPException(status_code=404, detail={"code": "schedule_block_not_found", "message": "Stundenplanblock nicht gefunden"})
    row.active = False
    session.add(row)
    session.commit()
    return {"ok": True}


@router.post("/admin/schedule/seed-from-reference", dependencies=[Depends(require_admin)])
def admin_seed_from_reference(payload: SeedReferenceConfirm, session: Session = Depends(get_session)) -> dict:
    if payload.confirm != "RESET_STUNDENPLAN":
        raise HTTPException(status_code=400, detail={"code": "confirmation_required", "message": "Zum Zurücksetzen confirm=RESET_STUNDENPLAN senden."})
    children = {child.name: child for child in session.exec(select(Child)).all()}
    seed_time_slots(session)
    seed_schedule_days(session, children)
    seed_schedule_blocks(session, children, reset_to_reference=True)
    session.commit()
    return build_current_schedule(session)
