import os
from typing import Dict, Any, List, Optional, Tuple
from fastapi import APIRouter, Depends, HTTPException
from modules.logger_tool import initialise_logger
from modules.auth.supabase_bearer import SupabaseBearer
import modules.database.tools.neo4j_driver_tools as driver_tools
from modules.database.supabase.utils.client import SupabaseServiceRoleClient

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


# ─── DB helpers ────────────────────────────────────────────────────────────────

def _user_to_teacher_db(user_id: str) -> str:
    return f"cc.users.teacher.{user_id.replace('-', '')}"


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


def _find_teacher_uuid(db: str, user_email: str) -> Optional[str]:
    """Query teacher UUID from a known Neo4j institute DB."""
    try:
        with driver_tools.get_session(database=db) as session:
            rec = session.run(
                'MATCH (t:Teacher) WHERE t.worker_email = $email '
                'RETURN t.uuid_string AS uuid LIMIT 1',
                email=user_email,
            ).single()
            if rec:
                return rec['uuid']
    except Exception:
        pass
    return None


def _resolve_institute(
    user_id: str, user_email: str
) -> tuple:
    """Returns (supabase_institute_id, neo4j_institute_db, neo4j_teacher_uuid).
    Supabase-first lookup with Neo4j email-scan fallback."""
    try:
        sb = _sb()
        p = sb.supabase.table('profiles').select('school_id').eq('id', user_id).single().execute()
        school_id = (p.data or {}).get('school_id')
        if school_id:
            i = sb.supabase.table('institutes').select('id,neo4j_uuid_string').eq('id', str(school_id)).single().execute()
            inst = i.data or {}
            neo4j_uuid = inst.get('neo4j_uuid_string')
            if neo4j_uuid:
                db = f'cc.institutes.{neo4j_uuid}'
                teacher_uuid = _find_teacher_uuid(db, user_email)
                return str(school_id), db, teacher_uuid
    except Exception as e:
        logger.warning(f'Supabase-first institute resolve failed: {e}')
    # Fallback: scan Neo4j
    db, teacher_uuid = _find_teacher_institute(user_email)
    return None, db, teacher_uuid


def _find_teacher_institute(user_email: str) -> Tuple[Optional[str], Optional[str]]:
    """Return (institute_db_name, teacher_uuid) by matching worker_email in all institute DBs."""
    if not user_email:
        return None, None
    try:
        with driver_tools.get_session(database="system") as session:
            result = session.run(
                "SHOW DATABASES YIELD name "
                "WHERE name STARTS WITH 'cc.institutes.' "
                "AND NOT name ENDS WITH '.curriculum' "
                "RETURN name"
            )
            dbs = [r["name"] for r in result]

        for db in dbs:
            try:
                with driver_tools.get_session(database=db) as session:
                    rec = session.run(
                        "MATCH (t:Teacher) WHERE t.worker_email = $email "
                        "RETURN t.uuid_string AS uuid LIMIT 1",
                        email=user_email,
                    ).single()
                    if rec and rec["uuid"]:
                        return db, rec["uuid"]
            except Exception:
                continue
    except Exception as e:
        logger.warning(f"Institute lookup failed: {e}")
    return None, None


# ─── Node queries ───────────────────────────────────────────────────────────────

def _query_user_node(teacher_db: str) -> Optional[Dict]:
    try:
        with driver_tools.get_session(database=teacher_db) as session:
            rec = session.run("MATCH (u:User) RETURN u LIMIT 1").single()
            if not rec:
                return None
            u = rec["u"]
            return {
                "neo4j_node_id": u["uuid_string"],
                "label": u.get("user_name") or u.get("cc_username") or "My Workspace",
                "node_type": "User",
                "neo4j_db_name": teacher_db,
                "is_section": False,
                "has_children": True,
            }
    except Exception as e:
        logger.warning(f"Could not query User node in {teacher_db}: {e}")
        return None


def _query_calendar_months(year: str) -> List[Dict]:
    try:
        with driver_tools.get_session(database="classroomcopilot") as session:
            result = session.run(
                "MATCH (y:CalendarYear {year: $year})-[:YEAR_INCLUDES_MONTH]->(m:CalendarMonth) "
                "RETURN m ORDER BY toInteger(m.month)",
                year=year,
            )
            return [
                {
                    "neo4j_node_id": r["m"]["uuid_string"],
                    "label": r["m"]["month_name"],
                    "node_type": "CalendarMonth",
                    "neo4j_db_name": "classroomcopilot",
                    "is_section": False,
                    "has_children": True,
                }
                for r in result
            ]
    except Exception as e:
        logger.error(f"Error querying calendar months for {year}: {e}")
        return []


def _query_month_days(month_uuid: str) -> List[Dict]:
    try:
        with driver_tools.get_session(database="classroomcopilot") as session:
            result = session.run(
                "MATCH (m:CalendarMonth {uuid_string: $mid})-[:MONTH_INCLUDES_DAY]->(d:CalendarDay) "
                "RETURN d ORDER BY d.date",
                mid=month_uuid,
            )
            return [
                {
                    "neo4j_node_id": r["d"]["uuid_string"],
                    "label": f"{r['d']['day_of_week'][:3]} {r['d']['iso_day']}",
                    "node_type": "CalendarDay",
                    "neo4j_db_name": "classroomcopilot",
                    "is_section": False,
                    "has_children": False,
                    "neo4j_props": {
                        "date": str(r["d"].get("date", "")),
                        "day_of_week": r["d"].get("day_of_week", ""),
                        "iso_day": r["d"].get("iso_day", ""),
                    },
                }
                for r in result
            ]
    except Exception as e:
        logger.error(f"Error querying days for month {month_uuid}: {e}")
        return []


def _query_teacher_classes(institute_db: str, teacher_uuid: str) -> List[Dict]:
    try:
        with driver_tools.get_session(database=institute_db) as session:
            result = session.run(
                "MATCH (t:Teacher {uuid_string: $uuid})-[:TEACHER_HAS_CLASS]->(c:SubjectClass) "
                "RETURN c ORDER BY c.name",
                uuid=teacher_uuid,
            )
            return [
                {
                    "neo4j_node_id": r["c"]["uuid_string"],
                    "label": r["c"].get("name") or "Class",
                    "node_type": "SubjectClass",
                    "neo4j_db_name": institute_db,
                    "is_section": False,
                    "has_children": True,
                }
                for r in result
            ]
    except Exception as e:
        logger.warning(f"Could not query classes for teacher {teacher_uuid}: {e}")
        return []


# ─── Section builders ───────────────────────────────────────────────────────────

def _section(section_id: str, label: str, db: str, status: str,
             has_children: bool = False, children: Optional[List] = None) -> Dict:
    return {
        "neo4j_node_id": f"section_{section_id}",
        "label": label,
        "node_type": "Section",
        "section_id": section_id,
        "neo4j_db_name": db,
        "is_section": True,
        "has_children": has_children,
        "status": status,
        **({"children": children} if children is not None else {}),
    }


def _build_calendar_section() -> Dict:
    try:
        with driver_tools.get_session(database="classroomcopilot") as session:
            rows = session.run(
                "MATCH (y:CalendarYear) RETURN y ORDER BY toInteger(y.year)"
            ).data()
        if not rows:
            return _section("calendar", "Calendar", "classroomcopilot", "empty")
        year_nodes = [
            {
                "neo4j_node_id": r["y"]["uuid_string"],
                "label": r["y"].get("year") or r["y"]["uuid_string"],
                "node_type": "CalendarYear",
                "neo4j_db_name": "classroomcopilot",
                "is_section": False,
                "has_children": True,
            }
            for r in rows
        ]
        return _section(
            "calendar", "Calendar", "classroomcopilot", "populated",
            has_children=True, children=year_nodes,
        )
    except Exception as e:
        logger.warning(f"Calendar section build failed: {e}")
        return _section("calendar", "Calendar", "classroomcopilot", "empty")


def _build_timetable_section(institute_db: Optional[str], teacher_uuid: Optional[str]) -> Dict:
    if not institute_db or not teacher_uuid:
        return _section("timetable", "My Timetable", "", "no_school")

    try:
        with driver_tools.get_session(database=institute_db) as session:
            rec = session.run(
                "MATCH (t:Teacher {uuid_string: $uuid})-[:HAS_TIMETABLE]->(tt) "
                "RETURN tt LIMIT 1",
                uuid=teacher_uuid,
            ).single()
            if rec:
                tt = rec["tt"]
                return {
                    **_section("timetable", "My Timetable", institute_db, "populated",
                               has_children=True),
                    "neo4j_node_id": tt["uuid_string"],
                    "node_type": "TeacherTimetable",
                    "is_section": True,
                }
    except Exception as e:
        logger.warning(f"Timetable query failed: {e}")

    return _section("timetable", "My Timetable", institute_db, "empty")


def _build_classes_section(institute_db: Optional[str], teacher_uuid: Optional[str]) -> Dict:
    if not institute_db or not teacher_uuid:
        return _section("classes", "My Classes", "", "no_school")

    classes = _query_teacher_classes(institute_db, teacher_uuid)
    if classes:
        return _section("classes", "My Classes", institute_db, "populated",
                        has_children=True, children=classes)
    return _section("classes", "My Classes", institute_db, "empty")


def _build_curriculum_section(institute_db: Optional[str]) -> Dict:
    if not institute_db:
        return _section("curriculum", "Curriculum", "", "no_school")

    # Check for curriculum DB
    curriculum_db = f"{institute_db}.curriculum"
    try:
        with driver_tools.get_session(database="system") as session:
            rec = session.run(
                "SHOW DATABASES YIELD name WHERE name = $db RETURN name",
                db=curriculum_db,
            ).single()
            if not rec:
                return _section("curriculum", "Curriculum", institute_db, "empty")

        with driver_tools.get_session(database=curriculum_db) as session:
            rec = session.run("MATCH (n) RETURN count(n) AS cnt").single()
            cnt = rec["cnt"] if rec else 0
            if cnt > 0:
                return _section("curriculum", "Curriculum", curriculum_db, "populated",
                                has_children=True)
    except Exception as e:
        logger.warning(f"Curriculum check failed: {e}")

    return _section("curriculum", "Curriculum", institute_db, "empty")


def _build_journal_section(teacher_db: str) -> Dict:
    try:
        with driver_tools.get_session(database=teacher_db) as session:
            rec = session.run("MATCH (j:Journal) RETURN j LIMIT 1").single()
            if rec:
                j = rec["j"]
                return {
                    **_section("journal", "Journal", teacher_db, "populated", has_children=True),
                    "neo4j_node_id": j["uuid_string"],
                    "node_type": "Journal",
                    "is_section": True,
                }
    except Exception as e:
        logger.warning(f"Journal query failed: {e}")

    return _section("journal", "Journal", teacher_db, "not_initialized")


def _build_planner_section(teacher_db: str) -> Dict:
    try:
        with driver_tools.get_session(database=teacher_db) as session:
            rec = session.run("MATCH (p:Planner) RETURN p LIMIT 1").single()
            if rec:
                p = rec["p"]
                return {
                    **_section("planner", "Planner", teacher_db, "populated", has_children=True),
                    "neo4j_node_id": p["uuid_string"],
                    "node_type": "Planner",
                    "is_section": True,
                }
    except Exception as e:
        logger.warning(f"Planner query failed: {e}")

    return _section("planner", "Planner", teacher_db, "not_initialized")


def _build_school_section(institute_db: str) -> Dict:
    try:
        with driver_tools.get_session(database=institute_db) as session:
            rec = session.run("MATCH (s:School) RETURN s LIMIT 1").single()
            if not rec:
                return _section("school", "My School", institute_db, "empty")
            s = rec["s"]
            name = s.get("name") or "My School"
            # Academic years under this school
            ay_rows = session.run(
                "MATCH (ay:AcademicYear) RETURN ay ORDER BY ay.year"
            ).data()
            children = [
                {
                    "neo4j_node_id": row["ay"]["uuid_string"],
                    "label": row["ay"].get("year") or "Academic Year",
                    "node_type": "AcademicYear",
                    "neo4j_db_name": institute_db,
                    "is_section": False,
                    "has_children": True,
                }
                for row in ay_rows
            ]
            return {
                **_section("school", name, institute_db, "populated",
                           has_children=len(children) > 0,
                           children=children if children else None),
                "neo4j_node_id": s["uuid_string"],
                "node_type": "School",
                "is_section": True,
            }
    except Exception as e:
        logger.warning(f"School query failed: {e}")

    return _section("school", "My School", institute_db, "empty")


# ─── Lazy children for expanded nodes ──────────────────────────────────────────

def _get_children_for_node(
    neo4j_node_id: str,
    neo4j_db_name: str,
    node_type: str,
    section_id: str = "",
) -> List[Dict]:
    # Calendar
    if node_type == "CalendarYear":
        return _query_calendar_months(neo4j_node_id)

    if node_type == "CalendarMonth":
        return _query_month_days(neo4j_node_id)

    # Section containers that need lazy loading
    if node_type == "Section":
        if section_id == "timetable" and neo4j_db_name:
            # Children of timetable section = classes
            try:
                with driver_tools.get_session(database=neo4j_db_name) as session:
                    result = session.run(
                        "MATCH (tt)-[:TIMETABLE_HAS_CLASS]->(c:SubjectClass) "
                        "WHERE tt.uuid_string = $id "
                        "RETURN c ORDER BY c.name",
                        id=neo4j_node_id,
                    )
                    return [
                        {
                            "neo4j_node_id": r["c"]["uuid_string"],
                            "label": r["c"].get("name") or "Class",
                            "node_type": "SubjectClass",
                            "neo4j_db_name": neo4j_db_name,
                            "is_section": False,
                            "has_children": True,
                        }
                        for r in result
                    ]
            except Exception as e:
                logger.warning(f"Timetable children query failed: {e}")
                return []

        if section_id == "curriculum" and neo4j_db_name:
            try:
                with driver_tools.get_session(database=neo4j_db_name) as session:
                    result = session.run(
                        "MATCH (s:KeyStageSyllabus) RETURN s ORDER BY s.key_stage"
                    )
                    rows = list(result)
                    if not rows:
                        result = session.run(
                            "MATCH (s:YearGroupSyllabus) RETURN s ORDER BY s.year_group"
                        )
                        rows = list(result)
                    return [
                        {
                            "neo4j_node_id": r[list(r.keys())[0]]["uuid_string"],
                            "label": r[list(r.keys())[0]].get("name") or r[list(r.keys())[0]].get("key_stage") or "Syllabus",
                            "node_type": list(r.keys())[0],
                            "neo4j_db_name": neo4j_db_name,
                            "is_section": False,
                            "has_children": True,
                        }
                        for r in rows
                    ]
            except Exception as e:
                logger.warning(f"Curriculum children query failed: {e}")
                return []

        if section_id == "school" and neo4j_db_name:
            try:
                with driver_tools.get_session(database=neo4j_db_name) as session:
                    result = session.run(
                        "MATCH (s:School {uuid_string: $id})-[:HAS_DEPARTMENT]->(d) "
                        "RETURN d ORDER BY d.name",
                        id=neo4j_node_id,
                    )
                    return [
                        {
                            "neo4j_node_id": r["d"]["uuid_string"],
                            "label": r["d"].get("name") or "Department",
                            "node_type": "Department",
                            "neo4j_db_name": neo4j_db_name,
                            "is_section": False,
                            "has_children": True,
                        }
                        for r in result
                    ]
            except Exception as e:
                logger.warning(f"School children query failed: {e}")
                return []

    # AcademicYear → terms
    if node_type == "AcademicYear" and neo4j_db_name:
        try:
            with driver_tools.get_session(database=neo4j_db_name) as session:
                result = session.run(
                    "MATCH (ay:AcademicYear {uuid_string: $id})"
                    "-[:ACADEMIC_YEAR_HAS_ACADEMIC_TERM]->(t:AcademicTerm) "
                    "RETURN t ORDER BY toInteger(t.term_number)",
                    id=neo4j_node_id,
                )
                return [
                    {
                        "neo4j_node_id": r["t"]["uuid_string"],
                        "label": r["t"]["term_name"],
                        "node_type": "AcademicTerm",
                        "neo4j_db_name": neo4j_db_name,
                        "is_section": False,
                        "has_children": True,
                    }
                    for r in result
                ]
        except Exception as e:
            logger.warning(f"AcademicYear children failed: {e}")
        return []

    # AcademicTerm → weeks
    if node_type == "AcademicTerm" and neo4j_db_name:
        try:
            with driver_tools.get_session(database=neo4j_db_name) as session:
                result = session.run(
                    "MATCH (t:AcademicTerm {uuid_string: $id})"
                    "-[:ACADEMIC_TERM_HAS_ACADEMIC_WEEK]->(w:AcademicWeek) "
                    "RETURN w ORDER BY toInteger(w.academic_week_number)",
                    id=neo4j_node_id,
                )
                return [
                    {
                        "neo4j_node_id": r["w"]["uuid_string"],
                        "label": f"Week {r['w']['academic_week_number']}",
                        "node_type": "AcademicWeek",
                        "neo4j_db_name": neo4j_db_name,
                        "is_section": False,
                        "has_children": False,
                    }
                    for r in result
                ]
        except Exception as e:
            logger.warning(f"AcademicTerm children failed: {e}")
        return []

    # SubjectClass → lessons
    if node_type == "SubjectClass" and neo4j_db_name:
        try:
            with driver_tools.get_session(database=neo4j_db_name) as session:
                result = session.run(
                    "MATCH (c:SubjectClass {uuid_string: $id})-[:CLASS_HAS_LESSON]->(l) "
                    "RETURN l ORDER BY l.date, l.start_time LIMIT 50",
                    id=neo4j_node_id,
                )
                return [
                    {
                        "neo4j_node_id": r["l"]["uuid_string"],
                        "label": r["l"].get("title") or r["l"].get("period_code") or "Lesson",
                        "node_type": "TimetableLesson",
                        "neo4j_db_name": neo4j_db_name,
                        "is_section": False,
                        "has_children": False,
                    }
                    for r in result
                ]
        except Exception as e:
            logger.warning(f"Class lessons query failed: {e}")

    return []


# ─── Endpoints ──────────────────────────────────────────────────────────────────

@router.get("/tree")
async def get_teacher_graph_tree(
    credentials: dict = Depends(SupabaseBearer()),
) -> Dict[str, Any]:
    user_id = credentials.get("sub", "")
    user_email = credentials.get("email", "")
    if not user_id:
        raise HTTPException(status_code=403, detail="Could not extract user_id from token")

    teacher_db = _user_to_teacher_db(user_id)

    user_node = _query_user_node(teacher_db) or {
        "neo4j_node_id": user_id,
        "label": "My Workspace",
        "node_type": "User",
        "neo4j_db_name": teacher_db,
        "is_section": False,
        "has_children": True,
    }

    _, institute_db, teacher_node_uuid = _resolve_institute(user_id, user_email)

    sections = [
        _build_calendar_section(),
        _build_timetable_section(institute_db, teacher_node_uuid),
        _build_classes_section(institute_db, teacher_node_uuid),
        _build_curriculum_section(institute_db),
        _build_journal_section(teacher_db),
        _build_planner_section(teacher_db),
    ]

    if institute_db:
        sections.append(_build_school_section(institute_db))

    return {
        "status": "success",
        "tree": {**user_node, "children": sections},
        "meta": {
            "institute_db": institute_db,
            "teacher_linked": institute_db is not None,
        },
    }


@router.get("/node/children")
async def get_node_children(
    neo4j_node_id: str,
    neo4j_db_name: str,
    node_type: str,
    section_id: str = "",
    credentials: dict = Depends(SupabaseBearer()),
) -> Dict[str, Any]:
    children = _get_children_for_node(neo4j_node_id, neo4j_db_name, node_type, section_id)
    return {"status": "success", "children": children}


@router.get("/calendar/academic")
async def get_academic_calendar(credentials: dict = Depends(SupabaseBearer())) -> Dict[str, Any]:
    user_id = credentials.get("sub", "")
    user_email = credentials.get("email", "")
    _, institute_db, _ = _resolve_institute(user_id, user_email)
    if not institute_db:
        return {"status": "no_school", "terms": []}
    try:
        with driver_tools.get_session(database=institute_db) as s:
            if not s.run("MATCH (ay:AcademicYear) RETURN ay LIMIT 1").single():
                return {"status": "empty", "terms": [], "institute_db": institute_db}
            terms = []
            for tr in s.run(
                "MATCH (ay:AcademicYear)-[:ACADEMIC_YEAR_HAS_ACADEMIC_TERM]->(t:AcademicTerm) "
                "RETURN t ORDER BY toInteger(t.term_number)"
            ):
                t = tr["t"]
                with driver_tools.get_session(database=institute_db) as s2:
                    weeks = [{
                        "neo4j_node_id": w["w"]["uuid_string"],
                        "label": f"Week {w['w']['academic_week_number']}",
                        "node_type": "AcademicWeek",
                        "neo4j_db_name": institute_db,
                        "is_section": False,
                        "has_children": False,
                    } for w in s2.run(
                        "MATCH (t:AcademicTerm {uuid_string: $tid})-[:ACADEMIC_TERM_HAS_ACADEMIC_WEEK]->(w:AcademicWeek) "
                        "RETURN w ORDER BY toInteger(w.academic_week_number)",
                        tid=t["uuid_string"]
                    )]
                terms.append({
                    "neo4j_node_id": t["uuid_string"],
                    "label": t["term_name"],
                    "node_type": "AcademicTerm",
                    "neo4j_db_name": institute_db,
                    "is_section": False,
                    "has_children": len(weeks) > 0,
                    "children": weeks,
                })
            return {"status": "populated", "terms": terms, "institute_db": institute_db}
    except Exception as e:
        logger.error(f"Academic calendar query failed: {e}")
        return {"status": "error", "terms": []}
