"""
Google Calendar, Meet & Drive API Client.

Production-quality client with:
- OAuth2 credentials from refresh token (no file-based auth)
- Google Calendar API v3 for events and calendars
- Google Meet REST API v2 for conference records, recordings, transcripts, participants
- Google Drive API v3 for downloading recording files and transcript docs
- Cached API service instances
- Structured error types
"""

import logging
import sys
import uuid
from datetime import datetime, timedelta, timezone
from typing import Any, Optional

from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

logger = logging.getLogger("gmeet_mcp_server.client")

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
    stream=sys.stderr,
)


# --- Error Types ---

class CalendarAPIError(Exception):
    """Base error for Google API failures."""

    def __init__(self, message: str, status_code: Optional[int] = None):
        self.status_code = status_code
        super().__init__(message)


class CalendarAuthError(CalendarAPIError):
    """Authentication/authorization failure."""
    pass


# --- Constants ---

SCOPES = [
    "https://www.googleapis.com/auth/calendar",
    "https://www.googleapis.com/auth/meetings.space.readonly",
    "https://www.googleapis.com/auth/drive.readonly",
]


# --- Client ---

class GMeetClient:
    """Google Calendar, Meet & Drive API client with OAuth2 refresh token auth.

    Builds credentials from client_id, client_secret, and refresh_token
    environment variables. No file-based auth required.
    """

    def __init__(self, client_id: str, client_secret: str, refresh_token: str):
        if not client_id:
            raise CalendarAuthError("client_id is required")
        if not client_secret:
            raise CalendarAuthError("client_secret is required")
        if not refresh_token:
            raise CalendarAuthError("refresh_token is required")

        self._creds = Credentials(
            token=None,
            refresh_token=refresh_token,
            token_uri="https://oauth2.googleapis.com/token",
            client_id=client_id,
            client_secret=client_secret,
            scopes=SCOPES,
        )
        self._calendar_service = None
        self._meet_service = None
        self._drive_service = None

    # -- Service accessors ---

    def _get_calendar_service(self):
        """Return a cached Google Calendar API service instance."""
        if self._calendar_service is None:
            self._calendar_service = build("calendar", "v3", credentials=self._creds)
        return self._calendar_service

    def _get_meet_service(self):
        """Return a cached Google Meet API v2 service instance."""
        if self._meet_service is None:
            self._meet_service = build("meet", "v2", credentials=self._creds)
        return self._meet_service

    def _get_drive_service(self):
        """Return a cached Google Drive API v3 service instance."""
        if self._drive_service is None:
            self._drive_service = build("drive", "v3", credentials=self._creds)
        return self._drive_service

    # -- Helpers ---

    @staticmethod
    def _extract_meet_url(event: dict[str, Any]) -> str:
        """Extract the Google Meet URL from event conferenceData."""
        conference = event.get("conferenceData", {})
        for entry_point in conference.get("entryPoints", []):
            if entry_point.get("entryPointType") == "video":
                return entry_point.get("uri", "")
        return ""

    @staticmethod
    def _format_event(event: dict[str, Any]) -> dict[str, Any]:
        """Format a raw Calendar API event into a clean dict."""
        meet_url = GMeetClient._extract_meet_url(event)
        return {
            "event_id": event.get("id", ""),
            "title": event.get("summary", ""),
            "description": event.get("description", ""),
            "start": event.get("start", {}).get("dateTime", event.get("start", {}).get("date", "")),
            "end": event.get("end", {}).get("dateTime", event.get("end", {}).get("date", "")),
            "meet_url": meet_url,
            "html_link": event.get("htmlLink", ""),
            "attendees": [
                {
                    "email": a.get("email", ""),
                    "response_status": a.get("responseStatus", "needsAction"),
                }
                for a in event.get("attendees", [])
            ],
            "organizer": event.get("organizer", {}).get("email", ""),
            "status": event.get("status", ""),
            "created": event.get("created", ""),
            "updated": event.get("updated", ""),
        }

    # -- Calendar API ---

    def list_events(
        self,
        max_results: int = 10,
        time_min: Optional[str] = None,
        calendar_id: str = "primary",
    ) -> list[dict[str, Any]]:
        """List upcoming calendar events."""
        try:
            service = self._get_calendar_service()
            if not time_min:
                time_min = datetime.now(timezone.utc).isoformat()

            result = (
                service.events()
                .list(
                    calendarId=calendar_id,
                    timeMin=time_min,
                    maxResults=max_results,
                    singleEvents=True,
                    orderBy="startTime",
                )
                .execute()
            )
            return [self._format_event(e) for e in result.get("items", [])]
        except HttpError as exc:
            raise CalendarAPIError(
                f"Failed to list events: {exc}", status_code=exc.resp.status
            ) from exc

    def create_event(
        self,
        title: str,
        start_time: str,
        duration_minutes: int = 30,
        attendees: Optional[list[str]] = None,
        description: str = "",
        calendar_id: str = "primary",
        add_meet_link: bool = True,
    ) -> dict[str, Any]:
        """Create a calendar event, optionally with a Google Meet link."""
        try:
            service = self._get_calendar_service()
            start_dt = datetime.fromisoformat(start_time)
            end_dt = start_dt + timedelta(minutes=duration_minutes)

            event_body: dict[str, Any] = {
                "summary": title,
                "description": description,
                "start": {"dateTime": start_dt.isoformat(), "timeZone": "UTC"},
                "end": {"dateTime": end_dt.isoformat(), "timeZone": "UTC"},
            }

            if add_meet_link:
                event_body["conferenceData"] = {
                    "createRequest": {
                        "requestId": str(uuid.uuid4()),
                        "conferenceSolutionKey": {"type": "hangoutsMeet"},
                    }
                }

            if attendees:
                event_body["attendees"] = [{"email": email} for email in attendees]

            event = (
                service.events()
                .insert(
                    calendarId=calendar_id,
                    body=event_body,
                    conferenceDataVersion=1 if add_meet_link else 0,
                )
                .execute()
            )
            return self._format_event(event)
        except HttpError as exc:
            raise CalendarAPIError(
                f"Failed to create event: {exc}", status_code=exc.resp.status
            ) from exc

    def update_event(
        self,
        event_id: str,
        updates: dict[str, Any],
        calendar_id: str = "primary",
    ) -> dict[str, Any]:
        """Update fields on an existing calendar event."""
        try:
            service = self._get_calendar_service()
            event = (
                service.events()
                .get(calendarId=calendar_id, eventId=event_id)
                .execute()
            )

            if "title" in updates:
                event["summary"] = updates["title"]
            if "description" in updates:
                event["description"] = updates["description"]
            if "start_time" in updates:
                event["start"] = {"dateTime": updates["start_time"], "timeZone": "UTC"}
            if "end_time" in updates:
                event["end"] = {"dateTime": updates["end_time"], "timeZone": "UTC"}
            if "attendees" in updates:
                event["attendees"] = [{"email": e} for e in updates["attendees"]]

            updated = (
                service.events()
                .update(calendarId=calendar_id, eventId=event_id, body=event)
                .execute()
            )
            return self._format_event(updated)
        except HttpError as exc:
            raise CalendarAPIError(
                f"Failed to update event {event_id}: {exc}",
                status_code=exc.resp.status,
            ) from exc

    def delete_event(
        self,
        event_id: str,
        calendar_id: str = "primary",
    ) -> dict[str, Any]:
        """Delete a calendar event."""
        try:
            service = self._get_calendar_service()
            service.events().delete(
                calendarId=calendar_id, eventId=event_id
            ).execute()
            return {"event_id": event_id, "status": "deleted"}
        except HttpError as exc:
            raise CalendarAPIError(
                f"Failed to delete event {event_id}: {exc}",
                status_code=exc.resp.status,
            ) from exc

    def get_event(
        self,
        event_id: str,
        calendar_id: str = "primary",
    ) -> dict[str, Any]:
        """Get a specific calendar event by ID."""
        try:
            service = self._get_calendar_service()
            event = (
                service.events()
                .get(calendarId=calendar_id, eventId=event_id)
                .execute()
            )
            return self._format_event(event)
        except HttpError as exc:
            raise CalendarAPIError(
                f"Failed to get event {event_id}: {exc}",
                status_code=exc.resp.status,
            ) from exc

    def add_attendee(
        self,
        event_id: str,
        email: str,
        calendar_id: str = "primary",
    ) -> dict[str, Any]:
        """Add an attendee to an existing event without removing current attendees."""
        try:
            service = self._get_calendar_service()
            event = (
                service.events()
                .get(calendarId=calendar_id, eventId=event_id)
                .execute()
            )

            attendees = event.get("attendees", [])
            existing_emails = {a.get("email", "") for a in attendees}
            if email not in existing_emails:
                attendees.append({"email": email})
                event["attendees"] = attendees

                event = (
                    service.events()
                    .update(calendarId=calendar_id, eventId=event_id, body=event)
                    .execute()
                )

            return self._format_event(event)
        except HttpError as exc:
            raise CalendarAPIError(
                f"Failed to add attendee to event {event_id}: {exc}",
                status_code=exc.resp.status,
            ) from exc

    def list_calendars(self) -> list[dict[str, Any]]:
        """List all calendars accessible to the authenticated user."""
        try:
            service = self._get_calendar_service()
            result = service.calendarList().list().execute()
            return [
                {
                    "calendar_id": cal.get("id", ""),
                    "summary": cal.get("summary", ""),
                    "description": cal.get("description", ""),
                    "primary": cal.get("primary", False),
                    "access_role": cal.get("accessRole", ""),
                    "time_zone": cal.get("timeZone", ""),
                }
                for cal in result.get("items", [])
            ]
        except HttpError as exc:
            raise CalendarAPIError(
                f"Failed to list calendars: {exc}", status_code=exc.resp.status
            ) from exc

    # -- Meet REST API v2 ---

    async def list_conference_records(self, max_results: int = 25) -> list[dict]:
        """List past Google Meet conference records."""
        try:
            service = self._get_meet_service()
            response = service.conferenceRecords().list(
                pageSize=max_results
            ).execute()
            records = []
            for rec in response.get("conferenceRecords", []):
                records.append({
                    "name": rec.get("name", ""),
                    "startTime": rec.get("startTime", ""),
                    "endTime": rec.get("endTime", ""),
                    "space": rec.get("space", ""),
                    "expireTime": rec.get("expireTime", ""),
                })
            return records
        except HttpError as exc:
            raise CalendarAPIError(
                f"Failed to list conference records: {exc}",
                status_code=exc.resp.status,
            ) from exc

    async def get_conference_participants(self, conference_record: str) -> list[dict]:
        """Get participants for a specific conference record."""
        try:
            service = self._get_meet_service()
            response = service.conferenceRecords().participants().list(
                parent=conference_record
            ).execute()
            participants = []
            for p in response.get("participants", []):
                participants.append({
                    "name": p.get("name", ""),
                    "earliestStartTime": p.get("earliestStartTime", ""),
                    "latestEndTime": p.get("latestEndTime", ""),
                    "signedinUser": p.get("signedinUser", {}),
                    "anonymousUser": p.get("anonymousUser", {}),
                    "phoneUser": p.get("phoneUser", {}),
                })
            return participants
        except HttpError as exc:
            raise CalendarAPIError(
                f"Failed to get participants: {exc}",
                status_code=exc.resp.status,
            ) from exc

    async def list_recordings(self, conference_record: str) -> list[dict]:
        """List recordings for a conference record."""
        try:
            service = self._get_meet_service()
            response = service.conferenceRecords().recordings().list(
                parent=conference_record
            ).execute()
            recordings = []
            for r in response.get("recordings", []):
                recordings.append({
                    "name": r.get("name", ""),
                    "state": r.get("state", ""),
                    "startTime": r.get("startTime", ""),
                    "endTime": r.get("endTime", ""),
                    "driveDestination": r.get("driveDestination", {}),
                })
            return recordings
        except HttpError as exc:
            raise CalendarAPIError(
                f"Failed to list recordings: {exc}",
                status_code=exc.resp.status,
            ) from exc

    async def list_transcripts(self, conference_record: str) -> list[dict]:
        """List transcripts for a conference record."""
        try:
            service = self._get_meet_service()
            response = service.conferenceRecords().transcripts().list(
                parent=conference_record
            ).execute()
            transcripts = []
            for t in response.get("transcripts", []):
                transcripts.append({
                    "name": t.get("name", ""),
                    "state": t.get("state", ""),
                    "startTime": t.get("startTime", ""),
                    "endTime": t.get("endTime", ""),
                    "docsDestination": t.get("docsDestination", {}),
                })
            return transcripts
        except HttpError as exc:
            raise CalendarAPIError(
                f"Failed to list transcripts: {exc}",
                status_code=exc.resp.status,
            ) from exc

    # -- Drive API ---

    async def download_recording_info(self, file_id: str) -> dict:
        """Get recording file info from Google Drive."""
        try:
            service = self._get_drive_service()
            file_info = service.files().get(
                fileId=file_id,
                fields="id,name,mimeType,size,webViewLink,webContentLink,createdTime"
            ).execute()
            return file_info
        except HttpError as exc:
            raise CalendarAPIError(
                f"Failed to get recording info: {exc}",
                status_code=exc.resp.status,
            ) from exc

    async def get_transcript_content(self, document_id: str) -> str:
        """Get transcript content from Google Docs via Drive export."""
        try:
            service = self._get_drive_service()
            response = service.files().export(
                fileId=document_id,
                mimeType="text/plain"
            ).execute()
            return response.decode("utf-8") if isinstance(response, bytes) else str(response)
        except HttpError as exc:
            raise CalendarAPIError(
                f"Failed to get transcript content: {exc}",
                status_code=exc.resp.status,
            ) from exc


# Backward-compatible alias
CalendarClient = GMeetClient
