from datetime import date, timedelta

from fastapi.testclient import TestClient
from sqlmodel import Session, select

from app.db.session import engine
from app.main import app
from app.models import Child, Exam, StudyPlanItem, TaskInstance, TaskStatus
from tests.conftest import admin_headers

client = TestClient(app)


def first_child_id() -> int:
    with Session(engine) as session:
        return session.exec(select(Child).order_by(Child.sort_order)).first().id


def test_exam_generates_three_study_items_and_calendar_entries():
    child_id = first_child_id()
    exam_date = date(2026, 6, 19)  # Friday
    response = client.post(
        "/api/admin/exams",
        headers=admin_headers(client),
        json={
            "child_id": child_id,
            "subject": "Mathe",
            "topic": "Einmaleins",
            "exam_date": exam_date.isoformat(),
            "study_sessions": 3,
            "include_weekends": False,
        },
    )
    assert response.status_code == 201

    exam_id = response.json()["exam"]["id"]

    with Session(engine) as session:
        items = session.exec(select(StudyPlanItem).where(StudyPlanItem.exam_id == exam_id).order_by(StudyPlanItem.study_date)).all()
        assert [item.study_date for item in items] == [date(2026, 6, 16), date(2026, 6, 17), date(2026, 6, 18)]
        task_ids = [item.task_instance_id for item in items]
        assert all(task_ids)
        tasks = session.exec(select(TaskInstance).where(TaskInstance.id.in_(task_ids))).all()  # type: ignore[attr-defined]
        assert {task.kind for task in tasks} == {"study"}

    calendar = client.get("/api/calendar", params={"start": "2026-06-15", "weeks": 3})
    assert calendar.status_code == 200
    days = calendar.json()["days"]
    assert len(days) == 21
    assert any(day["exams"] and any(exam["subject"] == "Mathe" and exam["topic"] == "Einmaleins" for exam in day["exams"]) for day in days)
    assert sum(1 for day in days for item in day["study_items"] if item["exam_id"] == exam_id) == 3


def test_study_plan_can_include_weekends():
    child_id = first_child_id()
    response = client.post(
        "/api/admin/exams",
        headers=admin_headers(client),
        json={
            "child_id": child_id,
            "subject": "Deutsch",
            "topic": "Diktat",
            "exam_date": "2026-06-15",
            "study_sessions": 3,
            "include_weekends": True,
        },
    )
    assert response.status_code == 201
    exam_id = response.json()["exam"]["id"]
    with Session(engine) as session:
        items = session.exec(select(StudyPlanItem).where(StudyPlanItem.exam_id == exam_id).order_by(StudyPlanItem.study_date)).all()
        assert [item.study_date for item in items] == [date(2026, 6, 12), date(2026, 6, 13), date(2026, 6, 14)]


def test_study_plan_excludes_weekends():
    child_id = first_child_id()
    response = client.post(
        "/api/admin/exams",
        headers=admin_headers(client),
        json={
            "child_id": child_id,
            "subject": "Deutsch",
            "topic": "Diktat",
            "exam_date": "2026-06-15",
            "study_sessions": 3,
            "include_weekends": False,
        },
    )
    assert response.status_code == 201
    exam_id = response.json()["exam"]["id"]
    with Session(engine) as session:
        items = session.exec(select(StudyPlanItem).where(StudyPlanItem.exam_id == exam_id).order_by(StudyPlanItem.study_date)).all()
        assert [item.study_date for item in items] == [date(2026, 6, 10), date(2026, 6, 11), date(2026, 6, 12)]


def test_cancelled_exam_hides_calendar_and_cancels_study_tasks():
    child_id = first_child_id()
    create = client.post(
        "/api/admin/exams",
        headers=admin_headers(client),
        json={
            "child_id": child_id,
            "subject": "NMG",
            "topic": "Wasser",
            "exam_date": "2026-06-19",
            "study_sessions": 3,
            "include_weekends": False,
        },
    )
    exam_id = create.json()["exam"]["id"]
    response = client.post(f"/api/admin/exams/{exam_id}/cancel", headers=admin_headers(client))
    assert response.status_code == 200

    with Session(engine) as session:
        exam = session.get(Exam, exam_id)
        assert exam.status == "cancelled"
        items = session.exec(select(StudyPlanItem).where(StudyPlanItem.exam_id == exam_id)).all()
        assert all(item.cancelled for item in items)
        task_ids = [item.task_instance_id for item in items]
        tasks = session.exec(select(TaskInstance).where(TaskInstance.id.in_(task_ids))).all()  # type: ignore[attr-defined]
        assert all(task.status == TaskStatus.cancelled for task in tasks)

    calendar = client.get("/api/calendar", params={"start": "2026-06-15", "weeks": 3}).json()
    assert not any(exam["id"] == exam_id for day in calendar["days"] for exam in day["exams"])
    assert not any(item["exam_id"] == exam_id for day in calendar["days"] for item in day["study_items"])


def test_study_items_appear_on_home_todo():
    child_id = first_child_id()
    client.post(
        "/api/admin/exams",
        headers=admin_headers(client),
        json={
            "child_id": child_id,
            "subject": "Mathe",
            "topic": "Geometrie",
            "exam_date": "2026-06-19",
            "study_sessions": 1,
            "include_weekends": False,
        },
    )
    dashboard = client.get("/api/dashboard", params={"date": "2026-06-18"})
    assert dashboard.status_code == 200
    all_open = [task for child in dashboard.json()["children"] for task in child["tasks"]["open"]]
    assert any(task["kind"] == "study" and "Geometrie" in task["title"] for task in all_open)



def test_calendar_events_are_informative_and_crud_persistent():
    child_id = first_child_id()
    create = client.post(
        "/api/admin/calendar-events",
        headers=admin_headers(client),
        json={"date": "2026-06-16", "title": "Informativ Test", "description": "Keine Timeline-Auswirkung", "child_id": child_id, "icon": "📌"},
    )
    assert create.status_code == 201
    event_id = create.json()["calendar_event"]["id"]

    calendar = client.get("/api/calendar", params={"start": "2026-06-16", "weeks": 1}).json()
    first_day = next(day for day in calendar["days"] if day["date"] == "2026-06-16")
    assert any(item["title"] == "Informativ Test" for item in first_day["calendar_events"])
    assert sum(len(day["study_items"]) for day in calendar["days"]) == 0

    update = client.patch(
        f"/api/admin/calendar-events/{event_id}",
        headers=admin_headers(client),
        json={"date": "2026-06-17", "title": "Informativ Neu", "description": "Bearbeitet", "icon": "⭐"},
    )
    assert update.status_code == 200
    calendar = client.get("/api/calendar", params={"start": "2026-06-16", "weeks": 1}).json()
    old_day = next(day for day in calendar["days"] if day["date"] == "2026-06-16")
    new_day = next(day for day in calendar["days"] if day["date"] == "2026-06-17")
    assert not any(item["title"] == "Informativ Test" for item in old_day["calendar_events"])
    assert any(item["title"] == "Informativ Neu" and item["description"] == "Bearbeitet" for item in new_day["calendar_events"])

    delete = client.delete(f"/api/admin/calendar-events/{event_id}", headers=admin_headers(client))
    assert delete.status_code == 200
    assert delete.json()["ok"] is True
    assert delete.json()["calendar_event"]["active"] is False
    calendar = client.get("/api/calendar", params={"start": "2026-06-16", "weeks": 1}).json()
    new_day = next(day for day in calendar["days"] if day["date"] == "2026-06-17")
    assert not any(item["title"] == "Informativ Neu" for item in new_day["calendar_events"])


def test_study_items_are_labelled_as_learning_not_exam():
    child_id = first_child_id()
    response = client.post(
        "/api/admin/exams",
        headers=admin_headers(client),
        json={"child_id": child_id, "subject": "Mathe", "topic": "Division", "exam_date": "2026-06-26", "study_sessions": 2, "include_weekends": False},
    )
    assert response.status_code == 201
    calendar = client.get("/api/calendar", params={"start": "2026-06-22", "weeks": 1}).json()
    exam_day = next(day for day in calendar["days"] if day["date"] == "2026-06-26")
    study_days = [day for day in calendar["days"] if day["study_items"]]
    assert exam_day["exams"][0]["subject"] == "Mathe"
    assert all(item["title"] == "Mathe lernen" for day in study_days for item in day["study_items"])
