"""KUPID Portal MCP Server.

Provides tools for accessing Korea University portal (KUPID):
- Login/session management
- Notice board (kind=11)
- Academic schedule (kind=89)
- Scholarship notices (kind=88)
- Search across all boards
- Library seat availability
- Personal timetable
- Course search & syllabus
- Canvas LMS (mylms.korea.ac.kr) integration
"""

import asyncio
import re
import sys
import logging
from dataclasses import asdict
from pathlib import Path
from typing import Any

import httpx
from dotenv import load_dotenv
from mcp.server.fastmcp import FastMCP

from .academic import resolve_year_semester
from .auth import login, clear_session, Session
from .scraper import fetch_notice_list, fetch_notice_detail, NoticeItem
from .library import (
    fetch_library_seats,
    fetch_all_seats,
    LIBRARY_CODES,
)
from .timetable import (
    fetch_timetable_day,
    fetch_full_timetable,
    timetable_to_ics,
)
from .courses import (
    search_courses,
    fetch_syllabus,
    fetch_departments,
    fetch_my_courses,
    COLLEGE_CODES,
)
from .dept_notices import fetch_dept_notice_list, fetch_dept_notice_detail
from .dept_registry import resolve_site, list_all_sites, DEFAULT_SITES
from .grades import fetch_all_grades
from .lms import (
    lms_login,
    LMSSession,
    _clear_lms_session,
    fetch_lms_courses,
    fetch_lms_assignments,
    fetch_lms_modules,
    fetch_lms_todo,
    fetch_lms_upcoming_events,
    fetch_lms_dashboard,
    fetch_lms_announcements,
    fetch_lms_grades,
    fetch_lms_submissions,
    fetch_lms_quizzes,
    fetch_lms_syllabus,
    download_lms_file,
    fetch_lms_boards,
    fetch_lms_board_posts,
    fetch_lms_board_post,
)
from . import __version__

# Load .env file (looks in cwd, then project root)
load_dotenv()

# Logging setup — file logging only to keep stdout clean for MCP protocol
log_dir = Path.home() / ".cache" / "ku-portal-mcp"
log_dir.mkdir(parents=True, exist_ok=True)

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[logging.FileHandler(log_dir / "server.log")],
)
logger = logging.getLogger(__name__)

server = FastMCP(
    "KU Portal",
    dependencies=["httpx", "beautifulsoup4", "lxml"],
)

# Module-level session cache with async locks to prevent race conditions
_session: Session | None = None
_lms_session: LMSSession | None = None
_session_lock = asyncio.Lock()
_lms_session_lock = asyncio.Lock()

# Errors that may indicate a stale session (worth retrying with fresh login)
_RETRIABLE = (
    httpx.HTTPError,
    ValueError,
    RuntimeError,
    AttributeError,
    KeyError,
    IndexError,
)


async def _get_session() -> Session:
    """Get or create a valid session. Proactive refresh near expiry."""
    global _session
    async with _session_lock:
        if _session and _session.is_valid and not _session.should_refresh:
            return _session
        if _session and _session.should_refresh:
            logger.info("KUPID session near expiry, proactively refreshing")
        elif _session:
            logger.info("KUPID session expired, re-logging in")
        _session = await login()
        return _session


async def _with_retry(fn, *args, **kwargs):
    """Execute fn with session, auto re-login on stale session and retry once."""
    try:
        session = await _get_session()
        return await fn(session, *args, **kwargs)
    except _RETRIABLE as e:
        logger.warning(
            f"KUPID request failed ({type(e).__name__}: {e}), retrying with fresh session"
        )
        global _session
        async with _session_lock:
            clear_session()
            _session = None
        session = await _get_session()
        return await fn(session, *args, **kwargs)


def _format_items(items: list[NoticeItem], count: int) -> list[dict]:
    return [
        {
            "index": item.index,
            "message_id": item.message_id,
            "title": item.title,
            "date": item.date,
            "writer": item.writer,
        }
        for item in items[:count]
    ]


# ──────────────────────────────────────────────
# Existing tools: Login / Notice / Schedule / Scholarship / Search
# ──────────────────────────────────────────────


@server.tool()
async def kupid_login() -> dict[str, Any]:
    """KUPID 포털에 로그인하고 세션을 확인합니다.

    환경변수 KU_PORTAL_ID, KU_PORTAL_PW가 설정되어 있어야 합니다.
    세션이 유효하면 캐시된 세션을 재사용합니다.
    """
    try:
        session = await _get_session()
        return {
            "success": True,
            "message": "KUPID 로그인 성공",
            "session_valid": session.is_valid,
        }
    except Exception as e:
        clear_session()
        return {"success": False, "message": f"로그인 실패: {e}"}


@server.tool()
async def kupid_get_notices(page: int = 1, count: int = 20) -> dict[str, Any]:
    """KUPID 포털의 공지사항 목록을 조회합니다.

    Args:
        page: 페이지 번호 (기본값: 1)
        count: 한 페이지당 항목 수 (기본값: 20)
    """
    try:

        async def _fetch(session):
            return await fetch_notice_list(session, kind="11", page=page, count=count)

        items = await _with_retry(_fetch)
        return {
            "success": True,
            "count": len(items),
            "notices": _format_items(items, count),
        }
    except Exception as e:
        logger.error(f"Failed to fetch notices: {e}")
        return {"success": False, "message": f"공지사항 조회 실패: {e}"}


@server.tool()
async def kupid_get_notice_detail(
    notice_id: str, message_id: str = ""
) -> dict[str, Any]:
    """KUPID 공지사항의 상세 내용을 조회합니다.

    Args:
        notice_id: 공지사항 index (kupid_get_notices 결과의 index 필드)
        message_id: 공지사항 message_id (kupid_get_notices 결과의 message_id 필드)
    """
    try:
        item = NoticeItem(
            index=notice_id,
            message_id=message_id,
            kind="11",
            title="",
            date="",
            writer="",
        )

        async def _fetch(session):
            return await fetch_notice_detail(session, item)

        detail = await _with_retry(_fetch)
        return {
            "success": True,
            "notice": {
                "id": detail.id,
                "title": detail.title,
                "date": detail.date,
                "writer": detail.writer,
                "content": detail.content,
                "attachments": detail.attachments,
                "url": detail.url,
            },
        }
    except Exception as e:
        logger.error(f"Failed to fetch notice detail: {e}")
        return {"success": False, "message": f"공지사항 상세 조회 실패: {e}"}


@server.tool()
async def kupid_get_schedules(page: int = 1, count: int = 20) -> dict[str, Any]:
    """KUPID 포털의 학사일정 목록을 조회합니다.

    Args:
        page: 페이지 번호 (기본값: 1)
        count: 한 페이지당 항목 수 (기본값: 20)
    """
    try:

        async def _fetch(session):
            return await fetch_notice_list(session, kind="89", page=page, count=count)

        items = await _with_retry(_fetch)
        return {
            "success": True,
            "count": len(items),
            "schedules": _format_items(items, count),
        }
    except Exception as e:
        logger.error(f"Failed to fetch schedules: {e}")
        return {"success": False, "message": f"학사일정 조회 실패: {e}"}


@server.tool()
async def kupid_get_schedule_detail(
    schedule_id: str, message_id: str = ""
) -> dict[str, Any]:
    """KUPID 학사일정의 상세 내용을 조회합니다.

    Args:
        schedule_id: 학사일정 index (kupid_get_schedules 결과의 index 필드)
        message_id: 학사일정 message_id (kupid_get_schedules 결과의 message_id 필드)
    """
    try:
        item = NoticeItem(
            index=schedule_id,
            message_id=message_id,
            kind="89",
            title="",
            date="",
            writer="",
        )

        async def _fetch(session):
            return await fetch_notice_detail(session, item)

        detail = await _with_retry(_fetch)
        return {
            "success": True,
            "schedule": {
                "id": detail.id,
                "title": detail.title,
                "date": detail.date,
                "writer": detail.writer,
                "content": detail.content,
                "attachments": detail.attachments,
                "url": detail.url,
            },
        }
    except Exception as e:
        logger.error(f"Failed to fetch schedule detail: {e}")
        return {"success": False, "message": f"학사일정 상세 조회 실패: {e}"}


@server.tool()
async def kupid_get_scholarships(page: int = 1, count: int = 20) -> dict[str, Any]:
    """KUPID 포털의 장학공지 목록을 조회합니다.

    Args:
        page: 페이지 번호 (기본값: 1)
        count: 한 페이지당 항목 수 (기본값: 20)
    """
    try:

        async def _fetch(session):
            return await fetch_notice_list(session, kind="88", page=page, count=count)

        items = await _with_retry(_fetch)
        return {
            "success": True,
            "count": len(items),
            "scholarships": _format_items(items, count),
        }
    except Exception as e:
        logger.error(f"Failed to fetch scholarships: {e}")
        return {"success": False, "message": f"장학공지 조회 실패: {e}"}


@server.tool()
async def kupid_get_scholarship_detail(
    scholarship_id: str, message_id: str = ""
) -> dict[str, Any]:
    """KUPID 장학공지의 상세 내용을 조회합니다.

    Args:
        scholarship_id: 장학공지 index (kupid_get_scholarships 결과의 index 필드)
        message_id: 장학공지 message_id (kupid_get_scholarships 결과의 message_id 필드)
    """
    try:
        item = NoticeItem(
            index=scholarship_id,
            message_id=message_id,
            kind="88",
            title="",
            date="",
            writer="",
        )

        async def _fetch(session):
            return await fetch_notice_detail(session, item)

        detail = await _with_retry(_fetch)
        return {
            "success": True,
            "scholarship": {
                "id": detail.id,
                "title": detail.title,
                "date": detail.date,
                "writer": detail.writer,
                "content": detail.content,
                "attachments": detail.attachments,
                "url": detail.url,
            },
        }
    except Exception as e:
        logger.error(f"Failed to fetch scholarship detail: {e}")
        return {"success": False, "message": f"장학공지 상세 조회 실패: {e}"}


@server.tool()
async def kupid_search(
    keyword: str, board: str = "all", count: int = 20
) -> dict[str, Any]:
    """KUPID 포털에서 키워드로 공지사항/학사일정/장학공지를 검색합니다.

    제목에 키워드가 포함된 항목을 반환합니다.

    Args:
        keyword: 검색할 키워드
        board: 검색 대상 ("all", "notice", "schedule", "scholarship")
        count: 최대 결과 수 (기본값: 20)
    """
    try:
        boards = {
            "notice": "11",
            "schedule": "89",
            "scholarship": "88",
        }
        if board == "all":
            targets = boards.items()
        elif board in boards:
            targets = [(board, boards[board])]
        else:
            return {
                "success": False,
                "message": f"잘못된 board: {board}. all/notice/schedule/scholarship 중 선택",
            }

        results = []
        kw_lower = keyword.lower()

        for board_name, kind in targets:

            async def _fetch(session, k=kind):
                return await fetch_notice_list(session, kind=k)

            items = await _with_retry(_fetch)
            for item in items:
                if kw_lower in item.title.lower():
                    results.append(
                        {
                            "board": board_name,
                            "index": item.index,
                            "message_id": item.message_id,
                            "title": item.title,
                            "date": item.date,
                            "writer": item.writer,
                        }
                    )

        results = results[:count]
        return {
            "success": True,
            "keyword": keyword,
            "count": len(results),
            "results": results,
        }
    except Exception as e:
        logger.error(f"Failed to search: {e}")
        return {"success": False, "message": f"검색 실패: {e}"}


# ──────────────────────────────────────────────
# New: Library seat availability (no auth required)
# ──────────────────────────────────────────────


@server.tool()
async def kupid_get_library_seats(library_name: str = "") -> dict[str, Any]:
    """고려대학교 도서관 열람실 좌석 현황을 조회합니다.

    인증 없이 실시간 좌석 현황을 확인할 수 있습니다.

    Args:
        library_name: 도서관 이름 필터 (빈 문자열이면 전체 도서관 조회)
            - 중앙도서관, 중앙광장, 백주년기념 학술정보관, 과학도서관, 하나스퀘어, 법학도서관
    """
    try:
        if library_name:
            # Find matching library code
            code = None
            for c, name in LIBRARY_CODES.items():
                if library_name in name or name in library_name:
                    code = c
                    break
            if not code:
                return {
                    "success": False,
                    "message": f"도서관을 찾을 수 없습니다: {library_name}",
                    "available_libraries": list(LIBRARY_CODES.values()),
                }
            rooms = await fetch_library_seats(code)
            library_data = {LIBRARY_CODES[code]: [asdict(r) for r in rooms]}
        else:
            all_data = await fetch_all_seats()
            library_data = {
                name: [asdict(r) for r in rooms] for name, rooms in all_data.items()
            }

        # Calculate totals
        total_seats = 0
        total_available = 0
        total_in_use = 0
        for rooms in library_data.values():
            for room in rooms:
                total_seats += room["total_seats"]
                total_available += room["available"]
                total_in_use += room["in_use"]

        return {
            "success": True,
            "libraries": library_data,
            "summary": {
                "total_seats": total_seats,
                "total_available": total_available,
                "total_in_use": total_in_use,
                "occupancy_rate": f"{(total_in_use / total_seats * 100):.1f}%"
                if total_seats
                else "0%",
            },
        }
    except Exception as e:
        logger.error(f"Failed to fetch library seats: {e}")
        return {"success": False, "message": f"도서관 좌석 조회 실패: {e}"}


# ──────────────────────────────────────────────
# New: Personal timetable (SSO required)
# ──────────────────────────────────────────────


@server.tool()
async def kupid_get_timetable(
    day: str = "all", ics_export: bool = False
) -> dict[str, Any]:
    """개인 수업시간표를 조회합니다 (SSO 로그인 필요).

    포털 메인 페이지의 시간표 위젯 데이터를 파싱합니다.

    Args:
        day: 요일 ("all"=전체, "mon"/"tue"/"wed"/"thu"/"fri")
        ics_export: True이면 ICS 캘린더 파일 내용도 포함
    """
    try:
        day_map = {"mon": 1, "tue": 2, "wed": 3, "thu": 4, "fri": 5}

        if day == "all":

            async def _fetch(session):
                return await fetch_full_timetable(session)

            entries = await _with_retry(_fetch)
        elif day in day_map:
            d = day_map[day]

            async def _fetch_day(session, d=d):
                return await fetch_timetable_day(session, d)

            entries = await _with_retry(_fetch_day)
        else:
            return {
                "success": False,
                "message": f"잘못된 요일: {day}. all/mon/tue/wed/thu/fri 중 선택",
            }

        result = {
            "success": True,
            "count": len(entries),
            "timetable": [
                {
                    "day": e.day_of_week,
                    "period": e.period,
                    "subject": e.subject_name,
                    "classroom": e.classroom,
                    "start_time": e.start_time,
                    "end_time": e.end_time,
                }
                for e in entries
            ],
        }

        if not entries:
            result["message"] = "등록된 수업이 없습니다"

        if ics_export and entries:
            result["ics_content"] = timetable_to_ics(entries)

        return result
    except Exception as e:
        logger.error(f"Failed to fetch timetable: {e}")
        return {"success": False, "message": f"시간표 조회 실패: {e}"}


# ──────────────────────────────────────────────
# New: Course search & syllabus (SSO required)
# ──────────────────────────────────────────────


@server.tool()
async def kupid_search_courses(
    year: str = "",
    semester: str = "",
    college: str = "",
    department: str = "",
    campus: str = "1",
) -> dict[str, Any]:
    """개설과목을 검색합니다 (SSO 로그인 필요).

    학과/단과대별로 개설된 과목을 조회합니다.
    단과대 코드가 비어있으면 사용 가능한 단과대 목록을 반환합니다.

    Args:
        year: 학년도 (기본값: 현재 학기 기준 자동 선택)
        semester: 학기 ("1"=1학기, "2"=2학기, "summer"=여름학기, "winter"=겨울학기)
        college: 단과대 코드 (예: "5720"=정보대학, ""이면 코드 목록 반환)
        department: 학과 코드 (예: "5722"=컴퓨터학과, ""이면 학과 목록 반환)
        campus: 캠퍼스 ("1"=서울, "2"=세종)
    """
    try:
        year, semester = resolve_year_semester(year, semester)

        # If no college specified, return available colleges
        if not college:
            return {
                "success": True,
                "message": "단과대 코드를 선택해주세요",
                "colleges": [
                    {"code": code, "name": name} for code, name in COLLEGE_CODES.items()
                ],
            }

        # If no department specified, fetch department list
        if not department:

            async def _fetch_depts(session, col=college):
                return await fetch_departments(session, col, year, semester)

            depts = await _with_retry(_fetch_depts)
            college_name = COLLEGE_CODES.get(college, college)
            return {
                "success": True,
                "message": f"{college_name}의 학과를 선택해주세요",
                "college": college_name,
                "departments": depts,
            }

        # Search courses
        async def _fetch_courses(session):
            return await search_courses(
                session,
                year=year,
                semester=semester,
                campus=campus,
                college=college,
                department=department,
            )

        courses = await _with_retry(_fetch_courses)
        return {
            "success": True,
            "count": len(courses),
            "courses": [
                {
                    "campus": c.campus,
                    "course_code": c.course_code,
                    "section": c.section,
                    "course_type": c.course_type,
                    "course_name": c.course_name,
                    "professor": c.professor,
                    "credits": c.credits,
                    "schedule": c.schedule,
                }
                for c in courses
            ],
        }
    except Exception as e:
        logger.error(f"Failed to search courses: {e}")
        return {"success": False, "message": f"개설과목 검색 실패: {e}"}


@server.tool()
async def kupid_get_syllabus(
    course_code: str,
    section: str = "00",
    year: str = "",
    semester: str = "",
) -> dict[str, Any]:
    """강의계획서를 조회합니다 (SSO 로그인 필요).

    Args:
        course_code: 학수번호 (예: "COSE101")
        section: 분반 (예: "02")
        year: 학년도 (기본값: 현재 학기 기준 자동 선택)
        semester: 학기 ("1"=1학기, "2"=2학기, "summer"=여름학기, "winter"=겨울학기)
    """
    try:
        year, semester = resolve_year_semester(year, semester)

        async def _fetch(session):
            return await fetch_syllabus(
                session,
                course_code=course_code,
                section=section,
                year=year,
                semester=semester,
            )

        content = await _with_retry(_fetch)

        if not content:
            return {
                "success": False,
                "message": f"강의계획서를 찾을 수 없습니다: {course_code} (분반 {section})",
            }

        return {
            "success": True,
            "course_code": course_code,
            "section": section,
            "year": year,
            "semester": semester,
            "content": content,
        }
    except Exception as e:
        logger.error(f"Failed to fetch syllabus: {e}")
        return {"success": False, "message": f"강의계획서 조회 실패: {e}"}


# ──────────────────────────────────────────────
# New: My enrolled courses (infodepot)
# ──────────────────────────────────────────────


@server.tool()
async def kupid_my_courses(year: str = "", semester: str = "") -> dict[str, Any]:
    """내 수강신청 내역을 조회합니다 (SSO 로그인 필요).

    학수번호, 강의시간, 강의실, 교수, 학점, 이수구분 등 상세 정보를 반환합니다.
    대학원 과목도 포함됩니다.

    Args:
        year: 학년도 (기본값: 현재 학기 기준 자동 선택)
        semester: 학기 ("1"=1학기, "2"=2학기, "summer"=여름학기, "winter"=겨울학기)
    """
    try:
        year, semester = resolve_year_semester(year, semester)

        async def _fetch(session):
            return await fetch_my_courses(session, year=year, semester=semester)

        courses, total_credits = await _with_retry(_fetch)
        return {
            "success": True,
            "year": year,
            "semester": semester,
            "total_credits": total_credits,
            "count": len(courses),
            "courses": [
                {
                    "course_code": c.course_code,
                    "section": c.section,
                    "course_type": c.course_type,
                    "course_name": c.course_name,
                    "professor": c.professor,
                    "credits": c.credits,
                    "schedule": c.schedule,
                    "retake": c.retake,
                    "status": c.status,
                    "grad_code": c.grad_code,
                    "dept_code": c.dept_code,
                }
                for c in courses
            ],
        }
    except Exception as e:
        logger.error(f"Failed to fetch my courses: {e}")
        return {"success": False, "message": f"수강신청 내역 조회 실패: {e}"}


@server.tool()
async def kupid_get_all_grades(year_term: str = "") -> dict[str, Any]:
    """전체 성적, 누적 GPA, 취득학점을 조회합니다 (SSO 로그인 필요).

    KUPID 학적/졸업 > 성적사항 > 전체성적조회 화면의 최종 확정 성적을 가져옵니다.

    Args:
        year_term: 조회할 학년도/학기 코드 (예: "20242R"). 비우면 전체 조회
    """
    try:

        async def _fetch(session):
            return await fetch_all_grades(session, year_term=year_term)

        page = await _with_retry(_fetch)
        latest_summary = page.summaries[-1] if page.summaries else None

        return {
            "success": True,
            "year_term": year_term or None,
            "available_year_terms": page.available_year_terms,
            "record_count": len(page.records),
            "records": [
                {
                    "year": record.year,
                    "term": record.term,
                    "course_code": record.course_code,
                    "course_name": record.course_name,
                    "completion_type": record.completion_type,
                    "course_type": record.course_type,
                    "credits": record.credits,
                    "score": record.score,
                    "grade": record.grade,
                    "gpa": record.gpa,
                    "retake_year": record.retake_year,
                    "retake_term": record.retake_term,
                    "retake_course": record.retake_course,
                    "deletion_type": record.deletion_type,
                }
                for record in page.records
            ],
            "summary_count": len(page.summaries),
            "summaries": [
                {
                    "year": summary.year,
                    "term": summary.term,
                    "major_registered_credits": summary.major_registered_credits,
                    "major_earned_credits": summary.major_earned_credits,
                    "prerequisite_earned_credits": summary.prerequisite_earned_credits,
                    "research_earned_credits": summary.research_earned_credits,
                    "total_grade_points": summary.total_grade_points,
                    "official_gpa": summary.official_gpa,
                    "overall_gpa": summary.overall_gpa,
                    "official_converted_score": summary.official_converted_score,
                    "rank_for_certificate": summary.rank_for_certificate,
                }
                for summary in page.summaries
            ],
            "latest_summary": (
                {
                    "year": latest_summary.year,
                    "term": latest_summary.term,
                    "major_registered_credits": latest_summary.major_registered_credits,
                    "major_earned_credits": latest_summary.major_earned_credits,
                    "prerequisite_earned_credits": latest_summary.prerequisite_earned_credits,
                    "research_earned_credits": latest_summary.research_earned_credits,
                    "total_grade_points": latest_summary.total_grade_points,
                    "official_gpa": latest_summary.official_gpa,
                    "overall_gpa": latest_summary.overall_gpa,
                    "official_converted_score": latest_summary.official_converted_score,
                    "rank_for_certificate": latest_summary.rank_for_certificate,
                }
                if latest_summary
                else None
            ),
        }
    except Exception as e:
        logger.error(f"Failed to fetch all grades: {e}")
        return {"success": False, "message": f"전체 성적 조회 실패: {e}"}


# ──────────────────────────────────────────────
# New: Department notices (no auth required)
# ──────────────────────────────────────────────


@server.tool()
async def kupid_dept_notices(
    site_name: str = "", page: int = 1, count: int = 20
) -> dict[str, Any]:
    """학과/대학원 홈페이지 공지사항을 조회합니다 (인증 불필요).

    고려대학교 학과 홈페이지의 공지사항 게시판을 스크래핑합니다.
    site_name을 지정하지 않으면 사용 가능한 사이트 목록을 반환합니다.

    환경변수 KU_DEPT_URLS로 소속 학과를 설정할 수 있습니다.
    형식: "라벨|URL,라벨|URL,..."
    예: "SW·AI융합대학원|https://gscit.korea.ac.kr/gscit/board/notice_master.do"

    Args:
        site_name: 사이트 이름 또는 키 (빈 문자열이면 사이트 목록 반환)
        page: 페이지 번호 (기본값: 1)
        count: 한 페이지당 항목 수 (기본값: 20)
    """
    try:
        if not site_name:
            sites = list_all_sites()
            if not sites:
                return {
                    "success": True,
                    "message": (
                        "설정된 학과 사이트가 없습니다. "
                        "환경변수 KU_DEPT_URLS를 설정하거나 site_name에 "
                        "내장 사이트 키(gscit_master, cs_under 등)를 지정하세요."
                    ),
                    "available_sites": [
                        {"key": k, "label": v["label"], "url": v["url"]}
                        for k, v in DEFAULT_SITES.items()
                    ],
                }
            return {
                "success": True,
                "message": "사이트를 선택해주세요",
                "sites": sites,
            }

        site = resolve_site(site_name)
        if not site:
            return {
                "success": False,
                "message": f"사이트를 찾을 수 없습니다: {site_name}",
                "available_sites": list_all_sites(),
            }

        offset = (page - 1) * count
        items = await fetch_dept_notice_list(site["url"], offset=offset, limit=count)
        return {
            "success": True,
            "site": site["label"],
            "page": page,
            "count": len(items),
            "notices": [
                {
                    "article_no": n.article_no,
                    "title": n.title,
                    "writer": n.writer,
                    "date": n.date,
                    "views": n.views,
                    "is_pinned": n.is_pinned,
                    "has_attachment": n.has_attachment,
                }
                for n in items
            ],
        }
    except Exception as e:
        logger.error(f"Failed to fetch dept notices: {e}")
        return {"success": False, "message": f"학과 공지사항 조회 실패: {e}"}


@server.tool()
async def kupid_dept_notice_detail(site_name: str, article_no: str) -> dict[str, Any]:
    """학과/대학원 공지사항의 상세 내용을 조회합니다 (인증 불필요).

    kupid_dept_notices로 조회한 공지의 상세 내용을 가져옵니다.

    Args:
        site_name: 사이트 이름 또는 키 (kupid_dept_notices에서 사용한 값)
        article_no: 게시글 번호 (kupid_dept_notices 결과의 article_no 필드)
    """
    try:
        site = resolve_site(site_name)
        if not site:
            return {
                "success": False,
                "message": f"사이트를 찾을 수 없습니다: {site_name}",
                "available_sites": list_all_sites(),
            }

        detail = await fetch_dept_notice_detail(site["url"], article_no)
        return {
            "success": True,
            "site": site["label"],
            "notice": {
                "article_no": detail.article_no,
                "title": detail.title,
                "content": detail.content,
                "attachments": detail.attachments,
                "url": detail.url,
            },
        }
    except Exception as e:
        logger.error(f"Failed to fetch dept notice detail: {e}")
        return {"success": False, "message": f"학과 공지사항 상세 조회 실패: {e}"}


# ──────────────────────────────────────────────
# New: Canvas LMS (mylms.korea.ac.kr)
# ──────────────────────────────────────────────


async def _get_lms_session() -> LMSSession:
    """Get or create a valid LMS session. Proactive refresh near expiry."""
    global _lms_session
    async with _lms_session_lock:
        if _lms_session and _lms_session.is_valid and not _lms_session.should_refresh:
            return _lms_session
        if _lms_session and _lms_session.should_refresh:
            logger.info("LMS session near expiry, proactively refreshing")
        elif _lms_session:
            logger.info("LMS session expired, re-logging in")
        import os

        user_id = os.environ.get("KU_PORTAL_ID", "")
        password = os.environ.get("KU_PORTAL_PW", "")
        _lms_session = await lms_login(user_id, password)
        return _lms_session


async def _lms_with_retry(fn, *args, **kwargs):
    """Execute LMS API call with auto re-login on auth failure."""
    try:
        session = await _get_lms_session()
        return await fn(session, *args, **kwargs)
    except _RETRIABLE as e:
        logger.warning(
            f"LMS request failed ({type(e).__name__}: {e}), retrying with fresh session"
        )
        global _lms_session
        async with _lms_session_lock:
            _clear_lms_session()
            _lms_session = None
        session = await _get_lms_session()
        return await fn(session, *args, **kwargs)


@server.tool()
async def kupid_lms_courses() -> dict[str, Any]:
    """Canvas LMS 수강과목 목록을 조회합니다.

    mylms.korea.ac.kr의 Canvas LMS에서 수강 중인 과목 목록을 가져옵니다.
    SSO 로그인이 필요합니다.
    """
    try:
        courses = await _lms_with_retry(fetch_lms_courses)
        return {
            "success": True,
            "count": len(courses),
            "courses": [
                {
                    "id": c.get("id"),
                    "name": c.get("name"),
                    "course_code": c.get("course_code"),
                    "term": c.get("term", {}).get("name") if c.get("term") else None,
                    "workflow_state": c.get("workflow_state"),
                }
                for c in courses
            ],
        }
    except Exception as e:
        logger.error(f"Failed to fetch LMS courses: {e}")
        return {"success": False, "message": f"LMS 수강과목 조회 실패: {e}"}


@server.tool()
async def kupid_lms_assignments(
    course_id: int, upcoming_only: bool = False
) -> dict[str, Any]:
    """Canvas LMS 과제 목록을 조회합니다.

    특정 과목의 전체 과제(assignments) 목록을 가져옵니다.
    기본적으로 완료/마감 과제 포함 전체를 반환합니다.
    kupid_lms_courses로 course_id를 먼저 확인하세요.

    Args:
        course_id: 과목 ID (kupid_lms_courses의 id 필드)
        upcoming_only: True이면 마감 전 과제만 표시 (기본값: False, 전체 과제 반환)
    """
    try:

        async def _fetch(session, cid=course_id, upcoming=upcoming_only):
            return await fetch_lms_assignments(session, cid, upcoming)

        assignments = await _lms_with_retry(_fetch)
        return {
            "success": True,
            "course_id": course_id,
            "count": len(assignments),
            "assignments": [
                {
                    "id": a.get("id"),
                    "name": a.get("name"),
                    "due_at": a.get("due_at"),
                    "points_possible": a.get("points_possible"),
                    "submission_types": a.get("submission_types"),
                    "description": (a.get("description") or "")[:500],
                    "html_url": a.get("html_url"),
                }
                for a in assignments
            ],
        }
    except Exception as e:
        logger.error(f"Failed to fetch LMS assignments: {e}")
        return {"success": False, "message": f"LMS 과제 조회 실패: {e}"}


@server.tool()
async def kupid_lms_modules(course_id: int) -> dict[str, Any]:
    """Canvas LMS 강의자료(모듈)를 조회합니다.

    주차별 강의 모듈과 포함된 자료를 가져옵니다.
    kupid_lms_courses로 course_id를 먼저 확인하세요.

    Args:
        course_id: 과목 ID (kupid_lms_courses의 id 필드)
    """
    try:

        async def _fetch(session, cid=course_id):
            return await fetch_lms_modules(session, cid)

        modules = await _lms_with_retry(_fetch)
        return {
            "success": True,
            "course_id": course_id,
            "count": len(modules),
            "modules": [
                {
                    "id": m.get("id"),
                    "name": m.get("name"),
                    "position": m.get("position"),
                    "state": m.get("state"),
                    "items_count": m.get("items_count"),
                    "items": [
                        {
                            "id": item.get("id"),
                            "title": item.get("title"),
                            "type": item.get("type"),
                            "content_id": item.get("content_id"),
                            "url": item.get("url"),
                            "html_url": item.get("html_url"),
                        }
                        for item in m.get("items", [])
                    ],
                }
                for m in modules
            ],
        }
    except Exception as e:
        logger.error(f"Failed to fetch LMS modules: {e}")
        return {"success": False, "message": f"LMS 모듈 조회 실패: {e}"}


@server.tool()
async def kupid_lms_todo() -> dict[str, Any]:
    """Canvas LMS 할 일(Todo) 목록을 조회합니다.

    마감이 다가오는 과제, 퀴즈 등을 보여줍니다.
    """
    try:

        async def _fetch_all(session):
            todos = await fetch_lms_todo(session)
            events = await fetch_lms_upcoming_events(session)
            return todos, events

        todos, events = await _lms_with_retry(_fetch_all)
        return {
            "success": True,
            "todo_count": len(todos),
            "todos": [
                {
                    "type": t.get("type"),
                    "assignment": {
                        "name": t.get("assignment", {}).get("name"),
                        "due_at": t.get("assignment", {}).get("due_at"),
                        "course_id": t.get("assignment", {}).get("course_id"),
                        "html_url": t.get("assignment", {}).get("html_url"),
                    }
                    if t.get("assignment")
                    else None,
                }
                for t in todos
            ],
            "upcoming_events_count": len(events),
            "upcoming_events": [
                {
                    "title": e.get("title"),
                    "start_at": e.get("start_at"),
                    "end_at": e.get("end_at"),
                    "html_url": e.get("html_url"),
                }
                for e in events
            ],
        }
    except Exception as e:
        logger.error(f"Failed to fetch LMS todo: {e}")
        return {"success": False, "message": f"LMS 할 일 조회 실패: {e}"}


@server.tool()
async def kupid_lms_dashboard() -> dict[str, Any]:
    """Canvas LMS 대시보드를 조회합니다.

    현재 수강 중인 과목 카드와 과제/이벤트 현황을 보여줍니다.
    """
    try:
        cards = await _lms_with_retry(fetch_lms_dashboard)
        course_ids = [c.get("id") for c in cards if c.get("id")]
        announcements = []
        if course_ids:
            try:
                announcements = await _lms_with_retry(
                    fetch_lms_announcements, course_ids[:10]
                )
            except Exception:
                pass  # Announcements may not be available

        return {
            "success": True,
            "courses": [
                {
                    "id": c.get("id"),
                    "name": c.get("shortName", c.get("longName")),
                    "course_code": c.get("courseCode"),
                    "term": c.get("term"),
                }
                for c in cards
            ],
            "announcements": [
                {
                    "id": a.get("id"),
                    "title": a.get("title"),
                    "posted_at": a.get("posted_at"),
                    "context_code": a.get("context_code"),
                    "message": (a.get("message") or "")[:300],
                }
                for a in announcements[:10]
            ],
        }
    except Exception as e:
        logger.error(f"Failed to fetch LMS dashboard: {e}")
        return {"success": False, "message": f"LMS 대시보드 조회 실패: {e}"}


@server.tool()
async def kupid_lms_grades(course_id: int) -> dict[str, Any]:
    """Canvas LMS 성적/점수를 조회합니다.

    과목별 현재 점수, 최종 점수, 학점(grade)을 확인합니다.
    kupid_lms_courses로 course_id를 먼저 확인하세요.

    Args:
        course_id: 과목 ID (kupid_lms_courses의 id 필드)
    """
    try:

        async def _fetch(session, cid=course_id):
            return await fetch_lms_grades(session, cid)

        enrollments = await _lms_with_retry(_fetch)
        return {
            "success": True,
            "course_id": course_id,
            "enrollments": [
                {
                    "type": e.get("type"),
                    "enrollment_state": e.get("enrollment_state"),
                    "grades": {
                        "current_score": e.get("grades", {}).get("current_score"),
                        "current_grade": e.get("grades", {}).get("current_grade"),
                        "final_score": e.get("grades", {}).get("final_score"),
                        "final_grade": e.get("grades", {}).get("final_grade"),
                    }
                    if e.get("grades")
                    else None,
                }
                for e in enrollments
            ],
        }
    except Exception as e:
        logger.error(f"Failed to fetch LMS grades: {e}")
        return {"success": False, "message": f"LMS 성적 조회 실패: {e}"}


@server.tool()
async def kupid_lms_submissions(course_id: int) -> dict[str, Any]:
    """Canvas LMS 과제 제출 현황을 조회합니다.

    과목의 전체 과제에 대한 제출 여부, 점수, 채점 상태를 확인합니다.
    kupid_lms_courses로 course_id를 먼저 확인하세요.

    Args:
        course_id: 과목 ID (kupid_lms_courses의 id 필드)
    """
    try:

        async def _fetch(session, cid=course_id):
            return await fetch_lms_submissions(session, cid)

        submissions = await _lms_with_retry(_fetch)
        return {
            "success": True,
            "course_id": course_id,
            "count": len(submissions),
            "submissions": [
                {
                    "assignment_id": s.get("assignment_id"),
                    "assignment_name": s.get("assignment", {}).get("name")
                    if s.get("assignment")
                    else None,
                    "due_at": s.get("assignment", {}).get("due_at")
                    if s.get("assignment")
                    else None,
                    "submitted_at": s.get("submitted_at"),
                    "workflow_state": s.get("workflow_state"),
                    "score": s.get("score"),
                    "grade": s.get("grade"),
                    "late": s.get("late"),
                    "missing": s.get("missing"),
                    "points_deducted": s.get("points_deducted"),
                    "comments_count": len(s.get("submission_comments", [])),
                }
                for s in submissions
            ],
        }
    except Exception as e:
        logger.error(f"Failed to fetch LMS submissions: {e}")
        return {"success": False, "message": f"LMS 제출 현황 조회 실패: {e}"}


@server.tool()
async def kupid_lms_quizzes(course_id: int) -> dict[str, Any]:
    """Canvas LMS 퀴즈/시험 목록을 조회합니다.

    과목의 퀴즈, 시험, 설문 목록과 마감일, 시간제한 등을 확인합니다.
    kupid_lms_courses로 course_id를 먼저 확인하세요.

    Args:
        course_id: 과목 ID (kupid_lms_courses의 id 필드)
    """
    try:

        async def _fetch(session, cid=course_id):
            return await fetch_lms_quizzes(session, cid)

        quizzes = await _lms_with_retry(_fetch)
        return {
            "success": True,
            "course_id": course_id,
            "count": len(quizzes),
            "quizzes": [
                {
                    "id": q.get("id"),
                    "title": q.get("title"),
                    "quiz_type": q.get("quiz_type"),
                    "due_at": q.get("due_at"),
                    "lock_at": q.get("lock_at"),
                    "time_limit": q.get("time_limit"),
                    "question_count": q.get("question_count"),
                    "points_possible": q.get("points_possible"),
                    "published": q.get("published"),
                    "html_url": q.get("html_url"),
                }
                for q in quizzes
            ],
        }
    except Exception as e:
        logger.error(f"Failed to fetch LMS quizzes: {e}")
        return {"success": False, "message": f"LMS 퀴즈 조회 실패: {e}"}


@server.tool()
async def kupid_lms_syllabus(
    course_code: str = "", course_id: int = 0
) -> dict[str, Any]:
    """Canvas LMS 수업 계획서(syllabus)를 조회합니다.

    과목의 수업 계획서 내용을 가져옵니다.
    course_code(예: BDC115) 또는 course_id 중 하나를 입력하세요.
    course_code를 입력하면 수강과목 목록에서 자동으로 course_id를 찾습니다.

    Args:
        course_code: 과목코드 (예: BDC115). course_id 대신 사용 가능
        course_id: 과목 ID (kupid_lms_courses의 id 필드). course_code 대신 사용 가능
    """
    from bs4 import BeautifulSoup

    if not course_code and not course_id:
        return {
            "success": False,
            "message": "course_code 또는 course_id 중 하나를 입력하세요.",
        }

    try:
        # course_code로 조회 시 course_id 찾기
        if course_code and not course_id:
            courses = await _lms_with_retry(fetch_lms_courses)
            keyword = course_code.upper()
            matched = [
                c
                for c in courses
                if keyword in (c.get("course_code") or "").upper()
                or keyword in (c.get("name") or "").upper()
            ]
            if not matched:
                # Fallback: 학수번호로 infodepot 수강과목에서 과목명 찾아 재매칭
                try:
                    session = await _get_session()
                    enrolled, _ = await fetch_my_courses(session)
                    enrolled_match = [
                        e for e in enrolled if keyword == e.course_code.upper()
                    ]
                    if enrolled_match:
                        enroll_name = enrolled_match[0].course_name
                        matched = [
                            c for c in courses if enroll_name in (c.get("name") or "")
                        ]
                except Exception:
                    pass

            if not matched:
                return {
                    "success": False,
                    "message": f"'{course_code}'에 해당하는 수강과목을 찾을 수 없습니다. "
                    "kupid_lms_courses로 수강과목 목록을 확인하세요.",
                }
            if len(matched) > 1:
                return {
                    "success": False,
                    "message": f"'{course_code}'에 해당하는 과목이 {len(matched)}개 있습니다. course_id를 지정하세요.",
                    "candidates": [
                        {
                            "id": c.get("id"),
                            "name": c.get("name"),
                            "course_code": c.get("course_code"),
                        }
                        for c in matched
                    ],
                }
            course_id = matched[0]["id"]

        async def _fetch(session, cid=course_id):
            return await fetch_lms_syllabus(session, cid)

        course_data = await _lms_with_retry(_fetch)

        syllabus_html = course_data.get("syllabus_body") or ""
        syllabus_text = ""

        if syllabus_html:
            soup = BeautifulSoup(syllabus_html, "lxml")

            # iframe이 infodepot을 가리키면 직접 fetch하여 구조화된 데이터 반환
            iframe = soup.find("iframe", src=re.compile(r"infodepot\.korea\.ac\.kr"))
            if iframe:
                iframe_src = iframe.get("src", "")
                from urllib.parse import urlparse

                parsed = urlparse(iframe_src)
                if parsed.scheme not in ("http", "https") or not parsed.netloc.endswith(
                    ".korea.ac.kr"
                ):
                    raise ValueError(f"Untrusted iframe src: {iframe_src!r}")
                try:
                    from .courses import (
                        _establish_infodepot_session,
                        parse_syllabus_structured,
                        INFODEPOT_BASE,
                    )
                    from .auth import _BROWSER_HEADERS

                    session = await _get_session()
                    async with httpx.AsyncClient(
                        timeout=30.0, follow_redirects=True
                    ) as client:
                        await _establish_infodepot_session(client, session)
                        resp = await client.get(
                            iframe_src,
                            headers={
                                **_BROWSER_HEADERS,
                                "referer": f"{INFODEPOT_BASE}/lecture/LecMajorSub.jsp",
                            },
                        )
                        html = resp.content.decode("euc-kr", errors="replace")
                        structured = parse_syllabus_structured(html)
                        if structured:
                            return {
                                "success": True,
                                "course_id": course_id,
                                "course_name": course_data.get("name"),
                                "course_code": course_data.get("course_code"),
                                "term": course_data.get("term", {}).get("name")
                                if course_data.get("term")
                                else None,
                                "syllabus_url": f"https://mylms.korea.ac.kr/courses/{course_id}/assignments/syllabus",
                                **structured,
                            }
                except Exception as e:
                    logger.debug(f"Failed to fetch iframe content: {e}")

            syllabus_text = soup.get_text(separator="\n", strip=True)

        return {
            "success": True,
            "course_id": course_id,
            "course_name": course_data.get("name"),
            "course_code": course_data.get("course_code"),
            "term": course_data.get("term", {}).get("name")
            if course_data.get("term")
            else None,
            "syllabus_url": f"https://mylms.korea.ac.kr/courses/{course_id}/assignments/syllabus",
            "syllabus": syllabus_text
            if syllabus_text
            else "(수업 계획서가 비어 있습니다)",
        }
    except Exception as e:
        logger.error(f"Failed to fetch LMS syllabus: {e}")
        return {"success": False, "message": f"LMS 수업 계획서 조회 실패: {e}"}


@server.tool()
async def kupid_lms_download_file(
    file_id: int,
    save_dir: str,
    filename: str = "",
) -> dict[str, Any]:
    """Canvas LMS 파일을 지정한 디렉토리에 다운로드합니다.

    file_id는 kupid_lms_modules 결과의 items에서 type이 'File'인 항목의
    content_id 필드에서 얻을 수 있습니다.

    Args:
        file_id: Canvas 파일 ID (items[*].content_id)
        save_dir: 저장할 디렉토리 절대경로 (예: /Users/me/Documents/lecture)
        filename: 저장 파일명 (생략 시 Canvas 원본 파일명 사용)
    """
    try:
        # Expand ~ and validate absolute path
        raw_path = save_dir.strip()
        if not raw_path:
            return {"success": False, "message": "save_dir가 비어 있습니다."}
        target_dir = Path(raw_path).expanduser()
        if not target_dir.is_absolute():
            return {
                "success": False,
                "message": f"save_dir는 절대경로여야 합니다: {save_dir}",
            }
        if ".." in target_dir.parts:
            return {
                "success": False,
                "message": "save_dir에 '..'를 포함할 수 없습니다.",
            }

        fname = filename.strip() or None

        async def _fetch(session, fid=file_id, d=target_dir, fn=fname):
            return await download_lms_file(session, fid, d, fn)

        result = await _lms_with_retry(_fetch)
        return {
            "success": True,
            "file_id": file_id,
            **result,
        }
    except Exception as e:
        logger.error(f"Failed to download LMS file {file_id}: {e}")
        return {"success": False, "message": f"LMS 파일 다운로드 실패: {e}"}


@server.tool()
async def kupid_lms_list_boards(course_id: int) -> dict[str, Any]:
    """Canvas LMS 과목의 게시판 목록을 조회합니다.

    Q&A 게시판, 강의자료실 등 교수님이 자료를 올리는 게시판들을 반환합니다.
    Canvas 네이티브 모듈(kupid_lms_modules)에 자료가 없으면 여기서 찾아보세요.

    Args:
        course_id: 과목 ID (kupid_lms_courses의 id 필드)
    """
    try:

        async def _fetch(session, cid=course_id):
            return await fetch_lms_boards(session, cid)

        boards = await _lms_with_retry(_fetch)
        return {
            "success": True,
            "course_id": course_id,
            "count": len(boards),
            "boards": [
                {
                    "id": b.get("id"),
                    "title": b.get("title"),
                    "type": b.get("type"),
                    "slug": b.get("slug"),
                    "use_attachment": b.get("use_attachment"),
                }
                for b in boards
            ],
        }
    except Exception as e:
        logger.error(f"Failed to list LMS boards: {e}")
        return {"success": False, "message": f"LMS 게시판 목록 조회 실패: {e}"}


@server.tool()
async def kupid_lms_list_board_posts(
    course_id: int,
    board_id: int,
    page: int = 1,
    keyword: str = "",
) -> dict[str, Any]:
    """게시판의 게시글 목록을 조회합니다.

    Args:
        course_id: 과목 ID
        board_id: 게시판 ID (kupid_lms_list_boards의 id 필드)
        page: 페이지 번호 (기본 1)
        keyword: 제목 검색어 (기본 전체)
    """
    try:

        async def _fetch(session, cid=course_id, bid=board_id, pg=page, kw=keyword):
            return await fetch_lms_board_posts(session, cid, bid, pg, kw)

        data = await _lms_with_retry(_fetch)
        items = data.get("items", []) if isinstance(data, dict) else []
        return {
            "success": True,
            "course_id": course_id,
            "board_id": board_id,
            "page": page,
            "count": len(items),
            "posts": [
                {
                    "id": p.get("id"),
                    "idx": p.get("idx"),
                    "title": p.get("title"),
                    "user_name": p.get("user_name"),
                    "attachment_count": p.get("attachment_count"),
                    "comment_count": p.get("comment_count"),
                    "view_count": p.get("view_count"),
                    "is_notice": p.get("is_notice"),
                    "created_at": p.get("created_at"),
                }
                for p in items
            ],
        }
    except Exception as e:
        logger.error(f"Failed to list board posts: {e}")
        return {"success": False, "message": f"게시글 목록 조회 실패: {e}"}


@server.tool()
async def kupid_lms_get_board_post(
    course_id: int,
    board_id: int,
    post_id: int,
) -> dict[str, Any]:
    """게시글 상세와 첨부파일 목록을 조회합니다.

    attachments의 canvas_file_id를 kupid_lms_download_file의 file_id로
    넘기면 파일을 다운로드할 수 있습니다.

    Args:
        course_id: 과목 ID
        board_id: 게시판 ID
        post_id: 게시글 ID (kupid_lms_list_board_posts의 id 필드)
    """
    try:

        async def _fetch(session, cid=course_id, bid=board_id, pid=post_id):
            return await fetch_lms_board_post(session, cid, bid, pid)

        post = await _lms_with_retry(_fetch)
        return {
            "success": True,
            "post": {
                "id": post.get("id"),
                "title": post.get("title"),
                "user_name": post.get("user_name"),
                "content": post.get("content"),
                "created_at": post.get("created_at"),
                "updated_at": post.get("updated_at"),
                "view_count": post.get("view_count"),
                "attachments": [
                    {
                        "id": a.get("id"),
                        "filename": a.get("filename"),
                        "filesize": a.get("filesize"),
                        "canvas_file_id": a.get("canvas_file_id"),
                    }
                    for a in post.get("attachments", [])
                ],
            },
        }
    except Exception as e:
        logger.error(f"Failed to get board post: {e}")
        return {"success": False, "message": f"게시글 조회 실패: {e}"}


def main():
    if "--version" in sys.argv or "-V" in sys.argv:
        print(f"ku-portal-mcp {__version__}")
        return
    if "--help" in sys.argv or "-h" in sys.argv:
        print("ku-portal-mcp - Korea University KUPID Portal MCP Server")
        print(f"Version: {__version__}")
        print("\nUsage:")
        print("  ku-portal-mcp          Start MCP server (stdio)")
        print("  ku-portal-mcp --version  Show version")
        print("\nEnvironment variables:")
        print("  KU_PORTAL_ID    KUPID portal ID")
        print("  KU_PORTAL_PW    KUPID portal password")
        return

    try:
        logger.info(f"Starting KU Portal MCP Server v{__version__}...")
        logger.info(f"Python: {sys.version}")
        server.run()
    except Exception as e:
        logger.error(f"Server error: {e}")
        import traceback

        logger.error(traceback.format_exc())
        sys.exit(1)


if __name__ == "__main__":
    main()
