"""Google Calendar API adapter.

Implements CalendarPort for Google Calendar using the Google Calendar API v3.
"""

from __future__ import annotations

from datetime import UTC, datetime, timedelta
from typing import TypedDict, cast

import httpx

from noteflow.config.constants import (
    DEFAULT_MEETING_TITLE,
    ERR_API_PREFIX,
    ERR_TOKEN_EXPIRED,
    HTTP_AUTHORIZATION,
    HTTP_BEARER_PREFIX,
    HTTP_STATUS_OK,
    HTTP_STATUS_UNAUTHORIZED,
)
from noteflow.domain.ports.calendar import CalendarEventInfo, CalendarPort
from noteflow.domain.value_objects import OAuthProvider
from noteflow.infrastructure.logging import get_logger, log_timing

logger = get_logger(__name__)


class _GoogleEventDateTime(TypedDict, total=False):
    dateTime: str
    date: str


class _GoogleAttendee(TypedDict, total=False):
    email: str


class _GoogleConferenceEntryPoint(TypedDict, total=False):
    entryPointType: str
    uri: str


class _GoogleConferenceData(TypedDict, total=False):
    entryPoints: list[_GoogleConferenceEntryPoint]


class _GoogleEvent(TypedDict, total=False):
    id: str
    summary: str
    start: _GoogleEventDateTime
    end: _GoogleEventDateTime
    attendees: list[_GoogleAttendee]
    recurringEventId: str
    location: str
    description: str
    hangoutLink: str
    conferenceData: _GoogleConferenceData


class _GoogleEventsResponse(TypedDict, total=False):
    items: list[_GoogleEvent]


class GoogleCalendarError(Exception):
    """Google Calendar API error."""


class GoogleCalendarAdapter(CalendarPort):
    """Google Calendar API adapter.

    Fetches calendar events and user info using Google Calendar API v3.
    Requires a valid OAuth access token with calendar.readonly scope.
    """

    CALENDAR_API_BASE = "https://www.googleapis.com/calendar/v3"
    USERINFO_API_URL = "https://www.googleapis.com/oauth2/v2/userinfo"

    async def list_events(
        self,
        access_token: str,
        hours_ahead: int = 24,
        limit: int = 20,
    ) -> list[CalendarEventInfo]:
        """Fetch upcoming calendar events from Google Calendar.

        Args:
            access_token: Google OAuth token with calendar.readonly scope.
            hours_ahead: Number of hours to look ahead.
            limit: Maximum events to fetch (capped by API).

        Returns:
            List of Google Calendar events ordered by start time.

        Raises:
            GoogleCalendarError: If API call fails.
        """
        now = datetime.now(UTC)
        time_min = now.isoformat()
        time_max = (now + timedelta(hours=hours_ahead)).isoformat()

        url = f"{self.CALENDAR_API_BASE}/calendars/primary/events"
        params: dict[str, str | int] = {
            "timeMin": time_min,
            "timeMax": time_max,
            "maxResults": limit,
            "singleEvents": "true",
            "orderBy": "startTime",
        }

        headers = {HTTP_AUTHORIZATION: f"{HTTP_BEARER_PREFIX}{access_token}"}

        with log_timing(
            "google_calendar_list_events",
            hours_ahead=hours_ahead,
            limit=limit,
        ):
            async with httpx.AsyncClient() as client:
                response = await client.get(url, params=params, headers=headers)

        if response.status_code == HTTP_STATUS_UNAUTHORIZED:
            raise GoogleCalendarError(ERR_TOKEN_EXPIRED)

        if response.status_code != HTTP_STATUS_OK:
            error_msg = response.text
            logger.error("Google Calendar API error: %s", error_msg)
            raise GoogleCalendarError(f"{ERR_API_PREFIX}{error_msg}")

        data_value = response.json()
        if not isinstance(data_value, dict):
            logger.warning("Unexpected Google Calendar response payload")
            return []
        data = cast(_GoogleEventsResponse, data_value)
        items = data.get("items", [])
        logger.info(
            "google_calendar_events_fetched",
            event_count=len(items),
            hours_ahead=hours_ahead,
        )

        return [self._parse_event(item) for item in items]

    async def get_user_email(self, access_token: str) -> str:
        """Get authenticated user's email address.

        Args:
            access_token: Valid OAuth access token.

        Returns:
            User's email address.

        Raises:
            GoogleCalendarError: If API call fails.
        """
        headers = {HTTP_AUTHORIZATION: f"{HTTP_BEARER_PREFIX}{access_token}"}

        async with httpx.AsyncClient() as client:
            response = await client.get(self.USERINFO_API_URL, headers=headers)

        if response.status_code == HTTP_STATUS_UNAUTHORIZED:
            raise GoogleCalendarError(ERR_TOKEN_EXPIRED)

        if response.status_code != HTTP_STATUS_OK:
            error_msg = response.text
            logger.error("Google userinfo API error: %s", error_msg)
            raise GoogleCalendarError(f"{ERR_API_PREFIX}{error_msg}")

        data_value = response.json()
        if not isinstance(data_value, dict):
            raise GoogleCalendarError("Invalid userinfo response")
        data = cast(dict[str, object], data_value)
        if email := data.get("email"):
            return str(email)
        else:
            raise GoogleCalendarError("No email in userinfo response")

    def _parse_event(self, item: _GoogleEvent) -> CalendarEventInfo:
        """Parse Google Calendar event into CalendarEventInfo."""
        event_id = str(item.get("id", ""))
        title = str(item.get("summary", DEFAULT_MEETING_TITLE))

        # Parse start/end times
        start_data = item.get("start", {})
        end_data = item.get("end", {})

        is_all_day = "date" in start_data
        start_time = self._parse_datetime(start_data)
        end_time = self._parse_datetime(end_data)

        # Parse attendees
        attendees_data = item.get("attendees", [])
        attendees = tuple(
            str(attendee.get("email", ""))
            for attendee in attendees_data
            if attendee.get("email")
        )

        # Extract meeting URL from conferenceData or hangoutLink
        meeting_url = self._extract_meeting_url(item)

        # Check if recurring
        is_recurring = bool(item.get("recurringEventId"))

        location = item.get("location")
        description = item.get("description")

        return CalendarEventInfo(
            id=event_id,
            title=title,
            start_time=start_time,
            end_time=end_time,
            attendees=attendees,
            location=str(location) if location else None,
            description=str(description) if description else None,
            meeting_url=meeting_url,
            is_recurring=is_recurring,
            is_all_day=is_all_day,
            provider=OAuthProvider.GOOGLE,
            raw=dict(item),
        )

    def _parse_datetime(self, dt_data: _GoogleEventDateTime) -> datetime:
        """Parse datetime from Google Calendar format."""
        # All-day events use "date", timed events use "dateTime"
        dt_str = dt_data.get("dateTime") or dt_data.get("date")

        if not dt_str:
            return datetime.now(UTC)

        # Handle Z suffix for UTC
        if dt_str.endswith("Z"):
            dt_str = f"{dt_str[:-1]}+00:00"

        try:
            return datetime.fromisoformat(dt_str)
        except ValueError:
            logger.warning("Failed to parse datetime: %s", dt_str)
            return datetime.now(UTC)

    def _extract_meeting_url(self, item: _GoogleEvent) -> str | None:
        """Extract video meeting URL from event data."""
        if hangout_link := item.get("hangoutLink"):
            return hangout_link

        if conference_data := item.get("conferenceData"):
            entry_points = conference_data.get("entryPoints", [])
            for entry in entry_points:
                if entry.get("entryPointType") == "video":
                    if uri := entry.get("uri"):
                        return uri

        return None
