"""Jon's Zoom MCP server for meetings, recordings, summaries, and transcripts."""

import os
import base64
import json
from datetime import datetime, timedelta
from typing import Optional
from urllib.parse import quote

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

# Load .env from the same directory as this script
load_dotenv(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env"))

ZOOM_ACCOUNT_ID = os.environ["ZOOM_ACCOUNT_ID"]
ZOOM_CLIENT_ID = os.environ["ZOOM_CLIENT_ID"]
ZOOM_CLIENT_SECRET = os.environ["ZOOM_CLIENT_SECRET"]
ZOOM_API = "https://api.zoom.us/v2"

# Token cache
_token: Optional[str] = None
_token_expiry: Optional[datetime] = None


def get_access_token() -> str:
    """Get a valid Zoom access token, refreshing if needed."""
    global _token, _token_expiry

    if _token and _token_expiry and datetime.now() < (_token_expiry - timedelta(minutes=5)):
        return _token

    credentials = base64.b64encode(f"{ZOOM_CLIENT_ID}:{ZOOM_CLIENT_SECRET}".encode()).decode()

    with httpx.Client() as client:
        resp = client.post(
            "https://zoom.us/oauth/token",
            headers={
                "Authorization": f"Basic {credentials}",
                "Content-Type": "application/x-www-form-urlencoded",
            },
            data={
                "grant_type": "account_credentials",
                "account_id": ZOOM_ACCOUNT_ID,
            },
            timeout=10.0,
        )
        resp.raise_for_status()
        data = resp.json()

    _token = data["access_token"]
    _token_expiry = datetime.now() + timedelta(seconds=data["expires_in"])
    return _token


def zoom_request(method: str, path: str, **kwargs) -> dict:
    """Make an authenticated request to the Zoom API."""
    token = get_access_token()
    with httpx.Client() as client:
        resp = client.request(
            method,
            f"{ZOOM_API}{path}",
            headers={
                "Authorization": f"Bearer {token}",
                "Content-Type": "application/json",
            },
            timeout=15.0,
            **kwargs,
        )
        resp.raise_for_status()
        if resp.status_code == 204:
            return {"status": "success"}
        return resp.json()


def zoom_download_text(url: str) -> str:
    """Download a Zoom text artifact using the current OAuth token."""
    token = get_access_token()
    with httpx.Client(follow_redirects=True) as client:
        resp = client.get(
            url,
            headers={"Authorization": f"Bearer {token}"},
            timeout=30.0,
        )
        resp.raise_for_status()
        return resp.text


def meeting_path_id(meeting_id: str) -> str:
    """Encode numeric meeting IDs and UUIDs safely for Zoom API paths."""
    encoded = quote(str(meeting_id), safe="")
    if str(meeting_id).startswith("/") or "//" in str(meeting_id):
        encoded = quote(encoded, safe="")
    return encoded


# --- MCP Server ---

mcp = FastMCP(
    "Zoom",
    instructions=(
        "Manage Zoom meetings and read cloud recordings, AI meeting summaries, "
        "and retained meeting transcripts."
    ),
)


@mcp.tool()
def list_meetings(
    type: str = "upcoming",
    page_size: int = 30,
) -> str:
    """List Zoom meetings.

    Args:
        type: Meeting type filter — "scheduled", "live", "upcoming", or "upcoming_meetings". Default "upcoming".
        page_size: Number of results per page (max 300). Default 30.
    """
    data = zoom_request("GET", "/users/me/meetings", params={"type": type, "page_size": page_size})
    meetings = data.get("meetings", [])
    if not meetings:
        return "No meetings found."

    results = []
    for m in meetings:
        results.append({
            "id": m["id"],
            "topic": m["topic"],
            "start_time": m.get("start_time", "N/A"),
            "duration": m.get("duration", "N/A"),
            "join_url": m.get("join_url", ""),
            "status": m.get("status", ""),
        })
    return json.dumps(results, indent=2)


@mcp.tool()
def create_meeting(
    topic: str,
    start_time: str,
    duration: int = 60,
    timezone: str = "America/New_York",
    agenda: str = "",
    meeting_invitees: str = "",
) -> str:
    """Create a new Zoom meeting.

    Args:
        topic: Meeting title/subject.
        start_time: Start time in ISO 8601 format, e.g. "2026-03-15T14:00:00".
        duration: Duration in minutes. Default 60.
        timezone: Timezone string. Default "America/New_York".
        agenda: Optional meeting description/agenda.
        meeting_invitees: Comma-separated email addresses of invitees (they'll get Zoom calendar invites).
    """
    body = {
        "topic": topic,
        "type": 2,  # scheduled meeting
        "start_time": start_time,
        "duration": duration,
        "timezone": timezone,
        "agenda": agenda,
        "settings": {
            "join_before_host": True,
            "waiting_room": False,
            "auto_recording": "none",
            "meeting_authentication": False,
        },
    }

    if meeting_invitees:
        emails = [e.strip() for e in meeting_invitees.split(",") if e.strip()]
        body["settings"]["meeting_invitees"] = [{"email": e} for e in emails]

    data = zoom_request("POST", "/users/me/meetings", json=body)
    return json.dumps({
        "id": data["id"],
        "topic": data["topic"],
        "start_time": data["start_time"],
        "join_url": data["join_url"],
        "start_url": data["start_url"],
        "password": data.get("password", ""),
    }, indent=2)


@mcp.tool()
def get_meeting(meeting_id: int) -> str:
    """Get details for a specific Zoom meeting.

    Args:
        meeting_id: The Zoom meeting ID (numeric).
    """
    data = zoom_request("GET", f"/meetings/{meeting_id}")
    return json.dumps({
        "id": data["id"],
        "topic": data["topic"],
        "start_time": data.get("start_time", ""),
        "duration": data.get("duration", ""),
        "timezone": data.get("timezone", ""),
        "agenda": data.get("agenda", ""),
        "join_url": data.get("join_url", ""),
        "start_url": data.get("start_url", ""),
        "password": data.get("password", ""),
        "status": data.get("status", ""),
        "settings": {
            "waiting_room": data.get("settings", {}).get("waiting_room"),
            "join_before_host": data.get("settings", {}).get("join_before_host"),
            "auto_recording": data.get("settings", {}).get("auto_recording"),
        },
    }, indent=2)


@mcp.tool()
def update_meeting(
    meeting_id: int,
    topic: str = "",
    start_time: str = "",
    duration: int = 0,
    timezone: str = "",
    agenda: str = "",
) -> str:
    """Update an existing Zoom meeting. Only provide fields you want to change.

    Args:
        meeting_id: The Zoom meeting ID (numeric).
        topic: New meeting title. Leave empty to keep current.
        start_time: New start time in ISO 8601. Leave empty to keep current.
        duration: New duration in minutes. 0 to keep current.
        timezone: New timezone. Leave empty to keep current.
        agenda: New agenda/description. Leave empty to keep current.
    """
    body = {}
    if topic:
        body["topic"] = topic
    if start_time:
        body["start_time"] = start_time
    if duration:
        body["duration"] = duration
    if timezone:
        body["timezone"] = timezone
    if agenda:
        body["agenda"] = agenda

    if not body:
        return "No fields to update. Provide at least one field to change."

    zoom_request("PATCH", f"/meetings/{meeting_id}", json=body)
    return f"Meeting {meeting_id} updated successfully."


@mcp.tool()
def delete_meeting(meeting_id: int) -> str:
    """Delete a Zoom meeting.

    Args:
        meeting_id: The Zoom meeting ID (numeric).
    """
    zoom_request("DELETE", f"/meetings/{meeting_id}")
    return f"Meeting {meeting_id} deleted successfully."


@mcp.tool()
def list_cloud_recordings(
    from_date: str,
    to_date: str,
    page_size: int = 30,
    next_page_token: str = "",
) -> str:
    """List cloud recordings for the authenticated Zoom user.

    Args:
        from_date: Start date in YYYY-MM-DD format. Zoom permits at most one month per request.
        to_date: End date in YYYY-MM-DD format.
        page_size: Results per page, from 1 to 300. Default 30.
        next_page_token: Token returned by a previous page, if any.
    """
    params = {
        "from": from_date,
        "to": to_date,
        "page_size": max(1, min(page_size, 300)),
    }
    if next_page_token:
        params["next_page_token"] = next_page_token

    data = zoom_request("GET", "/users/me/recordings", params=params)
    meetings = []
    for meeting in data.get("meetings", []):
        meetings.append({
            "uuid": meeting.get("uuid"),
            "id": meeting.get("id"),
            "topic": meeting.get("topic"),
            "start_time": meeting.get("start_time"),
            "duration": meeting.get("duration"),
            "total_size": meeting.get("total_size"),
            "recording_count": meeting.get("recording_count"),
            "file_types": [
                file.get("file_type") for file in meeting.get("recording_files", [])
            ],
        })
    return json.dumps({
        "from": data.get("from", from_date),
        "to": data.get("to", to_date),
        "next_page_token": data.get("next_page_token", ""),
        "total_records": data.get("total_records", len(meetings)),
        "meetings": meetings,
    }, indent=2)


@mcp.tool()
def get_cloud_recording(meeting_id: str) -> str:
    """Get cloud recording metadata and file download URLs for a meeting ID or UUID."""
    data = zoom_request("GET", f"/meetings/{meeting_path_id(meeting_id)}/recordings")
    return json.dumps(data, indent=2)


@mcp.tool()
def list_meeting_summaries(
    from_time: str = "",
    to_time: str = "",
    page_size: int = 30,
    next_page_token: str = "",
) -> str:
    """List AI-generated meeting and webinar summaries for the account.

    Args:
        from_time: Optional RFC 3339 start time, such as 2026-07-01T00:00:00Z.
        to_time: Optional RFC 3339 end time.
        page_size: Results per page, from 1 to 300. Default 30.
        next_page_token: Token returned by a previous page, if any.
    """
    params = {"page_size": max(1, min(page_size, 300))}
    if from_time:
        params["from"] = from_time
    if to_time:
        params["to"] = to_time
    if next_page_token:
        params["next_page_token"] = next_page_token
    data = zoom_request("GET", "/meetings/meeting_summaries", params=params)
    return json.dumps(data, indent=2)


@mcp.tool()
def get_meeting_summary(meeting_id: str) -> str:
    """Get an AI-generated summary for a meeting ID or meeting UUID."""
    data = zoom_request(
        "GET",
        f"/meetings/{meeting_path_id(meeting_id)}/meeting_summary",
    )
    return json.dumps(data, indent=2)


@mcp.tool()
def get_meeting_transcript(meeting_id: str, include_content: bool = True) -> str:
    """Get a retained meeting transcript and optionally download its text.

    Args:
        meeting_id: Numeric meeting ID or meeting UUID.
        include_content: Download and include transcript text when available. Default true.
    """
    data = zoom_request(
        "GET",
        f"/meetings/{meeting_path_id(meeting_id)}/transcript",
    )
    result = {key: value for key, value in data.items() if key != "download_url"}
    download_url = data.get("download_url")
    if include_content and data.get("can_download") and download_url:
        result["transcript_content"] = zoom_download_text(download_url)
    else:
        result["download_available"] = bool(download_url)
    return json.dumps(result, indent=2)


def main():
    mcp.run()


if __name__ == "__main__":
    main()
