"""
Taught Lessons Router — materialization and lesson CRUD.

POST /materialize   — slot template × academic_periods → taught_lessons rows
GET  /lessons       — teacher's lessons for a date range
GET  /lessons/{id}  — single lesson detail
PATCH /lessons/{id} — update lesson_plan, notes, status (teacher-owned)
"""
import os
from collections import defaultdict
from datetime import datetime, date, timedelta
from typing import Any, Dict, List, Optional, Tuple
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from modules.logger_tool import initialise_logger
from modules.auth.supabase_bearer import SupabaseBearer
from modules.database.supabase.utils.client import SupabaseServiceRoleClient

logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
router = APIRouter()


def _sb() -> SupabaseServiceRoleClient:
    return SupabaseServiceRoleClient()


def _resolve_institute_id(user_id: str) -> Optional[str]:
    try:
        sb = _sb()
        p = sb.supabase.table("profiles").select("school_id").eq("id", user_id).single().execute()
        return str((p.data or {}).get("school_id") or "") or None
    except Exception:
        return None


def _require_institute(user_id: str) -> str:
    iid = _resolve_institute_id(user_id)
    if not iid:
        raise HTTPException(status_code=400, detail="User is not linked to a school")
    return iid


# ─── Request models ───────────────────────────────────────────────────────────

class UpdateLessonRequest(BaseModel):
    lesson_plan: Optional[Dict[str, Any]] = None
    notes: Optional[str] = None
    status: Optional[str] = None      # planned | in_progress | completed | cancelled | substituted


# ─── Materialize ─────────────────────────────────────────────────────────────

@router.post("/materialize")
async def materialize_taught_lessons(
    credentials: dict = Depends(SupabaseBearer()),
) -> Dict[str, Any]:
    """
    Materialize taught_lessons from teacher_timetable_slots × academic_periods.

    For each slot (day_of_week + period_code + week_cycle), find every
    academic_period that falls on a matching day and week cycle, then
    UPSERT a taught_lesson row and a whiteboard_room for it.

    Safe to re-run; uses ON CONFLICT DO UPDATE.
    """
    user_id = credentials.get("sub", "")
    institute_id = _require_institute(user_id)
    sb = _sb()

    # ── 1. Get teacher timetable ──────────────────────────────────────────────
    tt_rows = (
        sb.supabase.table("teacher_timetables")
        .select("id")
        .eq("profile_id", user_id)
        .eq("institute_id", institute_id)
        .limit(1)
        .execute()
        .data or []
    )
    if not tt_rows:
        return {"status": "error", "message": "No teacher timetable found — run /timetable/init first"}
    tt_id = tt_rows[0]["id"]

    # ── 2. Get teacher's timetable slots ──────────────────────────────────────
    slots = (
        sb.supabase.table("teacher_timetable_slots")
        .select("id,day_of_week,period_code,subject_class,start_time,end_time,week_cycle,class_id")
        .eq("teacher_timetable_id", tt_id)
        .execute()
        .data or []
    )
    if not slots:
        return {"status": "ok", "created": 0, "updated": 0, "message": "No timetable slots found"}

    # ── 3. Resolve class_ids for slots (match by subject_class name) ──────────
    class_name_to_id: Dict[str, str] = {}
    subject_names = list({s["subject_class"] for s in slots if s.get("subject_class")})
    if subject_names:
        try:
            classes_res = (
                sb.supabase.table("classes")
                .select("id,name,class_code")
                .eq("institute_id", institute_id)
                .eq("is_active", True)
                .execute()
                .data or []
            )
            for c in classes_res:
                if c.get("name"):
                    class_name_to_id[c["name"]] = c["id"]
                if c.get("class_code"):
                    class_name_to_id[c["class_code"]] = c["id"]
        except Exception as e:
            logger.warning(f"Could not resolve class names: {e}")

    # slot lookup keyed by (day_of_week, period_code, week_cycle)
    # week_cycle='' means applies to both A and B weeks
    slot_map: Dict[Tuple[str, str, str], Dict] = {}
    for slot in slots:
        key = (slot["day_of_week"], slot["period_code"], slot.get("week_cycle", ""))
        slot_map[key] = slot

    # ── 4. Get all academic_periods with day + week info ──────────────────────
    # Fetch academic_days and academic_weeks separately, then join in Python
    days_res = (
        sb.supabase.table("academic_days")
        .select("id,date,day_of_week,academic_week_id,day_type")
        .eq("institute_id", institute_id)
        .execute()
        .data or []
    )
    weeks_res = (
        sb.supabase.table("academic_weeks")
        .select("id,week_cycle")
        .eq("institute_id", institute_id)
        .execute()
        .data or []
    )
    week_cycle_map = {w["id"]: w["week_cycle"] for w in weeks_res}

    # Only academic days get lesson periods
    academic_day_map = {
        d["id"]: {**d, "week_cycle": week_cycle_map.get(d["academic_week_id"], "A")}
        for d in days_res
        if d["day_type"] == "Academic"
    }

    periods_res = (
        sb.supabase.table("academic_periods")
        .select("id,academic_day_id,period_code,period_name,start_time,end_time,period_type")
        .eq("institute_id", institute_id)
        .execute()
        .data or []
    )

    # ── 5. Match slots to periods ─────────────────────────────────────────────
    to_upsert: List[Dict] = []
    whiteboard_rows: List[Dict] = []

    for period in periods_res:
        day_info = academic_day_map.get(period["academic_day_id"])
        if not day_info:
            continue

        dow = day_info["day_of_week"]
        week_cycle = day_info["week_cycle"]
        pcode = period["period_code"]
        d = str(day_info["date"])[:10]

        # Check all matching slot patterns: exact week_cycle match or '' (both weeks)
        matched_slot = (
            slot_map.get((dow, pcode, week_cycle))
            or slot_map.get((dow, pcode, ""))
        )
        if not matched_slot:
            continue

        subj_class = matched_slot.get("subject_class", "")
        # Prefer the slot's own class_id, fall back to name-lookup
        class_id = (
            matched_slot.get("class_id")
            or class_name_to_id.get(subj_class)
        )

        tl_id_hint = f"tl_{period['id']}"   # deterministic for neo4j_node_id
        to_upsert.append({
            "academic_period_id": period["id"],
            "teacher_timetable_slot_id": matched_slot["id"],
            "class_id": class_id,            # may be None
            "teacher_id": user_id,
            "institute_id": institute_id,
            "date": d,
            "period_code": pcode,
            "week_cycle": week_cycle,
            "day_of_week": dow,
            "status": "planned",
            "neo4j_node_id": tl_id_hint,
        })

    if not to_upsert:
        return {"status": "ok", "created": 0, "updated": 0, "message": "No slot-period matches found"}

    # ── 6. Batch-upsert taught_lessons ────────────────────────────────────────
    BATCH = 100
    created = 0
    for i in range(0, len(to_upsert), BATCH):
        chunk = to_upsert[i : i + BATCH]
        try:
            res = (
                sb.supabase.table("taught_lessons")
                .upsert(chunk, on_conflict="academic_period_id,teacher_id")
                .execute()
            )
            created += len(res.data or [])
        except Exception as e:
            logger.error(f"taught_lessons upsert chunk {i}: {e}")

    # ── 7. Fetch newly-created taught_lesson ids, create whiteboard_rooms ─────
    try:
        tl_rows = (
            sb.supabase.table("taught_lessons")
            .select("id,date,period_code,class_id")
            .eq("teacher_id", user_id)
            .eq("institute_id", institute_id)
            .is_("whiteboard_room_id", "null")
            .execute()
            .data or []
        )

        # Build whiteboard_room rows
        wr_rows = []
        for tl in tl_rows:
            class_name = class_name_to_id and next(
                (name for name, cid in class_name_to_id.items() if cid == tl.get("class_id")),
                tl.get("period_code", "")
            )
            wr_rows.append({
                "user_id": user_id,
                "institute_id": institute_id,
                "name": f"{tl['date']} {tl['period_code']}",
                "context_type": "taught_lesson",
                "context_id": tl["id"],
                "storage_path": f"taught_lessons/{tl['id']}",
            })

        # Batch insert whiteboard_rooms
        wr_ids: Dict[str, str] = {}  # context_id → room_id
        for i in range(0, len(wr_rows), BATCH):
            chunk = wr_rows[i : i + BATCH]
            try:
                res = sb.supabase.table("whiteboard_rooms").insert(chunk).execute()
                for row in (res.data or []):
                    if row.get("context_id"):
                        wr_ids[row["context_id"]] = row["id"]
            except Exception as e:
                logger.warning(f"whiteboard_rooms batch insert: {e}")

        # Update taught_lessons with their whiteboard_room_id
        for context_id, room_id in wr_ids.items():
            try:
                sb.supabase.table("taught_lessons").update(
                    {"whiteboard_room_id": room_id}
                ).eq("id", context_id).execute()
            except Exception as e:
                logger.warning(f"whiteboard_room_id update for {context_id}: {e}")

        rooms_created = len(wr_ids)
    except Exception as e:
        logger.warning(f"Whiteboard room creation failed (non-fatal): {e}")
        rooms_created = 0

    logger.info(f"Materialized {created} taught_lessons, {rooms_created} whiteboard_rooms for teacher {user_id}")
    return {
        "status": "ok",
        "lessons_upserted": created,
        "whiteboard_rooms_created": rooms_created,
        "total_matches": len(to_upsert),
    }


# ─── Lesson timeline ─────────────────────────────────────────────────────────

@router.get("/lessons")
async def get_lessons(
    week_start: Optional[str] = None,   # ISO date "YYYY-MM-DD" (Monday); defaults to current week
    weeks: int = 1,                      # how many weeks to return (max 4)
    credentials: dict = Depends(SupabaseBearer()),
) -> Dict[str, Any]:
    """
    Return taught_lessons for the teacher within a date range, grouped by date.
    Defaults to the current week.
    """
    user_id = credentials.get("sub", "")
    institute_id = _require_institute(user_id)
    sb = _sb()

    # Resolve week window
    today = date.today()
    if week_start:
        try:
            monday = datetime.strptime(week_start, "%Y-%m-%d").date()
        except ValueError:
            monday = today - timedelta(days=today.weekday())
    else:
        monday = today - timedelta(days=today.weekday())

    weeks = min(max(weeks, 1), 4)
    friday = monday + timedelta(weeks=weeks, days=4)

    lessons = (
        sb.supabase.table("taught_lessons")
        .select(
            "id,date,period_code,week_cycle,day_of_week,status,lesson_plan,notes,whiteboard_room_id,"
            "class_id,academic_period_id"
        )
        .eq("teacher_id", user_id)
        .eq("institute_id", institute_id)
        .gte("date", str(monday))
        .lte("date", str(friday))
        .order("date")
        .order("period_code")
        .execute()
        .data or []
    )

    # Enrich with period time and class name
    period_ids = [l["academic_period_id"] for l in lessons if l.get("academic_period_id")]
    class_ids = list({l["class_id"] for l in lessons if l.get("class_id")})

    period_map: Dict[str, Dict] = {}
    class_map: Dict[str, Dict] = {}

    if period_ids:
        prows = (
            sb.supabase.table("academic_periods")
            .select("id,period_name,start_time,end_time")
            .in_("id", period_ids)
            .execute()
            .data or []
        )
        period_map = {r["id"]: r for r in prows}

    if class_ids:
        crows = (
            sb.supabase.table("classes")
            .select("id,name,class_code,subject,year_group")
            .in_("id", class_ids)
            .execute()
            .data or []
        )
        class_map = {r["id"]: r for r in crows}

    # Group by date
    days_map: Dict[str, List[Dict]] = defaultdict(list)
    for lesson in lessons:
        p = period_map.get(lesson.get("academic_period_id", ""), {})
        c = class_map.get(lesson.get("class_id", ""), {})
        enriched = {
            **lesson,
            "period_name": p.get("period_name", lesson["period_code"]),
            "start_time": p.get("start_time"),
            "end_time": p.get("end_time"),
            "class_name": c.get("name") or c.get("class_code"),
            "subject": c.get("subject"),
            "year_group": c.get("year_group"),
        }
        days_map[lesson["date"]].append(enriched)

    # Build ordered list of days
    days_list = []
    current = monday
    while current <= friday:
        d_str = str(current)
        days_list.append({
            "date": d_str,
            "day_of_week": current.strftime("%A"),
            "is_today": current == today,
            "lessons": days_map.get(d_str, []),
        })
        current += timedelta(days=1)
        # Skip weekends
        if current.weekday() >= 5:
            current += timedelta(days=7 - current.weekday())

    return {
        "status": "ok",
        "week_start": str(monday),
        "week_end": str(friday),
        "days": days_list,
        "total_lessons": len(lessons),
    }


@router.get("/lessons/{lesson_id}")
async def get_lesson(
    lesson_id: str,
    credentials: dict = Depends(SupabaseBearer()),
) -> Dict[str, Any]:
    user_id = credentials.get("sub", "")
    sb = _sb()

    res = (
        sb.supabase.table("taught_lessons")
        .select("*")
        .eq("id", lesson_id)
        .eq("teacher_id", user_id)
        .single()
        .execute()
    )
    if not res.data:
        raise HTTPException(status_code=404, detail="Lesson not found")
    return res.data


@router.patch("/lessons/{lesson_id}")
async def update_lesson(
    lesson_id: str,
    body: UpdateLessonRequest,
    credentials: dict = Depends(SupabaseBearer()),
) -> Dict[str, Any]:
    """Teacher updates their own lesson content: plan, notes, status."""
    user_id = credentials.get("sub", "")
    sb = _sb()

    updates: Dict[str, Any] = {}
    if body.lesson_plan is not None:
        updates["lesson_plan"] = body.lesson_plan
    if body.notes is not None:
        updates["notes"] = body.notes
    if body.status is not None:
        valid = {"planned", "in_progress", "completed", "cancelled", "substituted"}
        if body.status not in valid:
            raise HTTPException(status_code=400, detail=f"status must be one of {valid}")
        updates["status"] = body.status

    if not updates:
        raise HTTPException(status_code=400, detail="Nothing to update")

    updates["updated_at"] = datetime.utcnow().isoformat()
    res = (
        sb.supabase.table("taught_lessons")
        .update(updates)
        .eq("id", lesson_id)
        .eq("teacher_id", user_id)
        .execute()
    )
    if not res.data:
        raise HTTPException(status_code=404, detail="Lesson not found or access denied")
    return res.data[0]


# ─── Student lesson view ──────────────────────────────────────────────────────

@router.get("/student/lessons")
async def get_student_lessons(
    week_start: Optional[str] = None,
    weeks: int = 1,
    credentials: dict = Depends(SupabaseBearer()),
) -> Dict[str, Any]:
    """
    Return taught_lessons for a student's enrolled classes for a date range.
    Grouped by date, Mon-Fri only.
    """
    user_id = credentials.get("sub", "")
    institute_id = _resolve_institute_id(user_id)
    if not institute_id:
        return {"status": "error", "message": "Not linked to a school"}
    sb = _sb()

    today = date.today()
    if week_start:
        try:
            monday = datetime.strptime(week_start, "%Y-%m-%d").date()
        except ValueError:
            monday = today - timedelta(days=today.weekday())
    else:
        monday = today - timedelta(days=today.weekday())

    weeks = min(max(weeks, 1), 4)
    friday = monday + timedelta(weeks=weeks, days=4)

    # Get student's active class memberships
    memberships = (
        sb.supabase.table("class_students")
        .select("class_id")
        .eq("student_id", user_id)
        .eq("status", "active")
        .execute()
        .data or []
    )
    class_ids = [m["class_id"] for m in memberships]
    if not class_ids:
        days_list = []
        current = monday
        while current <= friday:
            days_list.append({
                "date": str(current),
                "day_of_week": current.strftime("%A"),
                "is_today": current == today,
                "lessons": [],
            })
            current += timedelta(days=1)
            if current.weekday() >= 5:
                current += timedelta(days=7 - current.weekday())
        return {"status": "ok", "week_start": str(monday), "days": days_list, "total_lessons": 0}

    # Query taught_lessons for those classes
    lessons = (
        sb.supabase.table("taught_lessons")
        .select(
            "id,date,period_code,week_cycle,day_of_week,status,lesson_plan,notes,whiteboard_room_id,"
            "class_id,academic_period_id,teacher_id"
        )
        .in_("class_id", class_ids)
        .eq("institute_id", institute_id)
        .gte("date", str(monday))
        .lte("date", str(friday))
        .order("date")
        .order("period_code")
        .execute()
        .data or []
    )

    # Enrich with period times, class name, teacher name
    period_ids = [l["academic_period_id"] for l in lessons if l.get("academic_period_id")]
    lesson_class_ids = list({l["class_id"] for l in lessons if l.get("class_id")})
    teacher_ids = list({l["teacher_id"] for l in lessons if l.get("teacher_id")})

    period_map: Dict[str, Dict] = {}
    class_map: Dict[str, Dict] = {}
    teacher_map: Dict[str, Dict] = {}

    if period_ids:
        prows = (
            sb.supabase.table("academic_periods")
            .select("id,period_name,start_time,end_time")
            .in_("id", period_ids)
            .execute()
            .data or []
        )
        period_map = {r["id"]: r for r in prows}

    if lesson_class_ids:
        crows = (
            sb.supabase.table("classes")
            .select("id,name,class_code,subject,year_group")
            .in_("id", lesson_class_ids)
            .execute()
            .data or []
        )
        class_map = {r["id"]: r for r in crows}

    if teacher_ids:
        trows = (
            sb.supabase.table("profiles")
            .select("id,full_name,display_name")
            .in_("id", teacher_ids)
            .execute()
            .data or []
        )
        teacher_map = {r["id"]: r for r in trows}

    from collections import defaultdict
    days_map: Dict[str, List[Dict]] = defaultdict(list)
    for lesson in lessons:
        p = period_map.get(lesson.get("academic_period_id", ""), {})
        c = class_map.get(lesson.get("class_id", ""), {})
        t = teacher_map.get(lesson.get("teacher_id", ""), {})
        enriched = {
            **lesson,
            "period_name": p.get("period_name", lesson["period_code"]),
            "start_time": p.get("start_time"),
            "end_time": p.get("end_time"),
            "class_name": c.get("name") or c.get("class_code"),
            "subject": c.get("subject"),
            "year_group": c.get("year_group"),
            "teacher_name": t.get("display_name") or t.get("full_name"),
        }
        days_map[lesson["date"]].append(enriched)

    days_list = []
    current = monday
    while current <= friday:
        d_str = str(current)
        days_list.append({
            "date": d_str,
            "day_of_week": current.strftime("%A"),
            "is_today": current == today,
            "lessons": days_map.get(d_str, []),
        })
        current += timedelta(days=1)
        if current.weekday() >= 5:
            current += timedelta(days=7 - current.weekday())

    return {
        "status": "ok",
        "week_start": str(monday),
        "week_end": str(friday),
        "days": days_list,
        "total_lessons": len(lessons),
    }
