import asyncio
import enum
import uuid
from datetime import datetime, timedelta, timezone
from typing import Literal

from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
from pydantic import BaseModel

from backend.blocks._base import (
    Block,
    BlockCategory,
    BlockOutput,
    BlockSchemaInput,
    BlockSchemaOutput,
)
from backend.data.model import SchemaField
from backend.util.settings import Settings

from ._auth import (
    GOOGLE_OAUTH_IS_CONFIGURED,
    TEST_CREDENTIALS,
    TEST_CREDENTIALS_INPUT,
    GoogleCredentials,
    GoogleCredentialsField,
    GoogleCredentialsInput,
)

settings = Settings()


class CalendarEvent(BaseModel):
    """Structured representation of a Google Calendar event."""

    id: str
    title: str
    start_time: str
    end_time: str
    is_all_day: bool
    location: str | None
    description: str | None
    organizer: str | None
    attendees: list[str]
    has_video_call: bool
    video_link: str | None
    calendar_link: str
    is_recurring: bool


class GoogleCalendarReadEventsBlock(Block):
    class Input(BlockSchemaInput):
        credentials: GoogleCredentialsInput = GoogleCredentialsField(
            ["https://www.googleapis.com/auth/calendar.readonly"]
        )
        calendar_id: str = SchemaField(
            description="Calendar ID (use 'primary' for your main calendar)",
            default="primary",
        )
        max_events: int = SchemaField(
            description="Maximum number of events to retrieve", default=10
        )
        start_time: datetime = SchemaField(
            description="Retrieve events starting from this time",
            default_factory=lambda: datetime.now(tz=timezone.utc),
        )
        time_range_days: int = SchemaField(
            description="Number of days to look ahead for events", default=30
        )
        search_term: str | None = SchemaField(
            description="Optional search term to filter events by", default=None
        )

        page_token: str | None = SchemaField(
            description="Page token from previous request to get the next batch of events. You can use this if you have lots of events you want to process in a loop",
            default=None,
        )
        include_declined_events: bool = SchemaField(
            description="Include events you've declined", default=False
        )

    class Output(BlockSchemaOutput):
        events: list[CalendarEvent] = SchemaField(
            description="List of calendar events in the requested time range",
            default_factory=list,
        )
        event: CalendarEvent = SchemaField(
            description="One of the calendar events in the requested time range"
        )
        next_page_token: str | None = SchemaField(
            description="Token for retrieving the next page of events if more exist",
            default=None,
        )
        error: str = SchemaField(
            description="Error message if the request failed",
        )

    def __init__(self):
        # Create realistic test data for events
        test_now = datetime.now(tz=timezone.utc)
        test_tomorrow = test_now + timedelta(days=1)

        test_event_dict = {
            "id": "event1id",
            "title": "Team Meeting",
            "start_time": test_tomorrow.strftime("%Y-%m-%d %H:%M"),
            "end_time": (test_tomorrow + timedelta(hours=1)).strftime("%Y-%m-%d %H:%M"),
            "is_all_day": False,
            "location": "Conference Room A",
            "description": "Weekly team sync",
            "organizer": "manager@example.com",
            "attendees": ["colleague1@example.com", "colleague2@example.com"],
            "has_video_call": True,
            "video_link": "https://meet.google.com/abc-defg-hij",
            "calendar_link": "https://calendar.google.com/calendar/event?eid=event1id",
            "is_recurring": True,
        }

        super().__init__(
            id="80bc3ed1-e9a4-449e-8163-a8fc86f74f6a",
            description="Retrieves upcoming events from a Google Calendar with filtering options",
            categories={BlockCategory.PRODUCTIVITY, BlockCategory.DATA},
            input_schema=GoogleCalendarReadEventsBlock.Input,
            output_schema=GoogleCalendarReadEventsBlock.Output,
            disabled=not GOOGLE_OAUTH_IS_CONFIGURED,
            test_input={
                "credentials": TEST_CREDENTIALS_INPUT,
                "calendar_id": "primary",
                "max_events": 5,
                "start_time": test_now.isoformat(),
                "time_range_days": 7,
                "search_term": None,
                "include_declined_events": False,
                "page_token": None,
            },
            test_credentials=TEST_CREDENTIALS,
            test_output=[
                ("event", test_event_dict),
                ("events", [test_event_dict]),
            ],
            test_mock={
                "_read_calendar": lambda *args, **kwargs: {
                    "items": [
                        {
                            "id": "event1id",
                            "summary": "Team Meeting",
                            "start": {
                                "dateTime": test_tomorrow.isoformat(),
                                "timeZone": "UTC",
                            },
                            "end": {
                                "dateTime": (
                                    test_tomorrow + timedelta(hours=1)
                                ).isoformat(),
                                "timeZone": "UTC",
                            },
                            "location": "Conference Room A",
                            "description": "Weekly team sync",
                            "organizer": {"email": "manager@example.com"},
                            "attendees": [
                                {"email": "colleague1@example.com"},
                                {"email": "colleague2@example.com"},
                            ],
                            "conferenceData": {
                                "conferenceUrl": "https://meet.google.com/abc-defg-hij"
                            },
                            "htmlLink": "https://calendar.google.com/calendar/event?eid=event1id",
                            "recurrence": ["RRULE:FREQ=WEEKLY;COUNT=10"],
                        }
                    ],
                    "nextPageToken": None,
                },
                "_format_events": lambda *args, **kwargs: [test_event_dict],
            },
        )

    async def run(
        self, input_data: Input, *, credentials: GoogleCredentials, **kwargs
    ) -> BlockOutput:
        try:
            service = self._build_service(credentials, **kwargs)

            # Calculate end time based on start time and time range
            end_time = input_data.start_time + timedelta(
                days=input_data.time_range_days
            )

            # Call Google Calendar API
            result = await asyncio.to_thread(
                self._read_calendar,
                service=service,
                calendarId=input_data.calendar_id,
                time_min=input_data.start_time.isoformat(),
                time_max=end_time.isoformat(),
                max_results=input_data.max_events,
                single_events=True,
                search_term=input_data.search_term,
                show_deleted=False,
                show_hidden=input_data.include_declined_events,
                page_token=input_data.page_token,
            )

            # Format events into a user-friendly structure
            formatted_events = self._format_events(result.get("items", []))

            # Include next page token if available
            if next_page_token := result.get("nextPageToken"):
                yield "next_page_token", next_page_token

            for event in formatted_events:
                yield "event", event

            yield "events", formatted_events

        except Exception as e:
            yield "error", str(e)

    @staticmethod
    def _build_service(credentials: GoogleCredentials, **kwargs):
        creds = Credentials(
            token=(
                credentials.access_token.get_secret_value()
                if credentials.access_token
                else None
            ),
            refresh_token=(
                credentials.refresh_token.get_secret_value()
                if credentials.refresh_token
                else None
            ),
            token_uri="https://oauth2.googleapis.com/token",
            client_id=settings.secrets.google_client_id,
            client_secret=settings.secrets.google_client_secret,
            scopes=credentials.scopes,
        )
        return build("calendar", "v3", credentials=creds)

    def _read_calendar(
        self,
        service,
        calendarId: str,
        time_min: str,
        time_max: str,
        max_results: int,
        single_events: bool,
        search_term: str | None = None,
        show_deleted: bool = False,
        show_hidden: bool = False,
        page_token: str | None = None,
    ) -> dict:
        """Read calendar events with optional filtering."""
        calendar = service.events()

        # Build query parameters
        params = {
            "calendarId": calendarId,
            "timeMin": time_min,
            "timeMax": time_max,
            "maxResults": max_results,
            "singleEvents": single_events,
            "orderBy": "startTime",
            "showDeleted": show_deleted,
            "showHiddenInvitations": show_hidden,
            **({"pageToken": page_token} if page_token else {}),
        }

        # Add search term if provided
        if search_term:
            params["q"] = search_term

        result = calendar.list(**params).execute()
        return result

    def _format_events(self, events: list[dict]) -> list[CalendarEvent]:
        """Format Google Calendar API events into user-friendly structure."""
        formatted_events = []

        for event in events:
            # Determine if all-day event
            is_all_day = "date" in event.get("start", {})

            # Format start and end times
            if is_all_day:
                start_time = event.get("start", {}).get("date", "")
                end_time = event.get("end", {}).get("date", "")
            else:
                # Convert ISO format to more readable format
                start_datetime = datetime.fromisoformat(
                    event.get("start", {}).get("dateTime", "").replace("Z", "+00:00")
                )
                end_datetime = datetime.fromisoformat(
                    event.get("end", {}).get("dateTime", "").replace("Z", "+00:00")
                )
                start_time = start_datetime.strftime("%Y-%m-%d %H:%M")
                end_time = end_datetime.strftime("%Y-%m-%d %H:%M")

            # Extract attendees
            attendees = []
            for attendee in event.get("attendees", []):
                if email := attendee.get("email"):
                    attendees.append(email)

            # Check for video call link
            has_video_call = False
            video_link = None
            if conf_data := event.get("conferenceData"):
                if conf_url := conf_data.get("conferenceUrl"):
                    has_video_call = True
                    video_link = conf_url
                elif entry_points := conf_data.get("entryPoints", []):
                    for entry in entry_points:
                        if entry.get("entryPointType") == "video":
                            has_video_call = True
                            video_link = entry.get("uri")
                            break

            # Create formatted event
            formatted_event = CalendarEvent(
                id=event.get("id", ""),
                title=event.get("summary", "Untitled Event"),
                start_time=start_time,
                end_time=end_time,
                is_all_day=is_all_day,
                location=event.get("location"),
                description=event.get("description"),
                organizer=event.get("organizer", {}).get("email"),
                attendees=attendees,
                has_video_call=has_video_call,
                video_link=video_link,
                calendar_link=event.get("htmlLink", ""),
                is_recurring=bool(event.get("recurrence")),
            )

            formatted_events.append(formatted_event)

        return formatted_events


class ReminderPreset(enum.Enum):
    """Common reminder times before an event."""

    TEN_MINUTES = 10
    THIRTY_MINUTES = 30
    ONE_HOUR = 60
    ONE_DAY = 1440  # 24 hours in minutes


class RecurrenceFrequency(enum.Enum):
    """Frequency options for recurring events."""

    DAILY = "DAILY"
    WEEKLY = "WEEKLY"
    MONTHLY = "MONTHLY"
    YEARLY = "YEARLY"


class ExactTiming(BaseModel):
    """Model for specifying start and end times."""

    discriminator: Literal["exact_timing"]
    start_datetime: datetime
    end_datetime: datetime


class DurationTiming(BaseModel):
    """Model for specifying start time and duration."""

    discriminator: Literal["duration_timing"]
    start_datetime: datetime
    duration_minutes: int


class OneTimeEvent(BaseModel):
    """Model for a one-time event."""

    discriminator: Literal["one_time"]


class RecurringEvent(BaseModel):
    """Model for a recurring event."""

    discriminator: Literal["recurring"]
    frequency: RecurrenceFrequency
    count: int


class GoogleCalendarCreateEventBlock(Block):
    class Input(BlockSchemaInput):
        credentials: GoogleCredentialsInput = GoogleCredentialsField(
            ["https://www.googleapis.com/auth/calendar"]
        )
        # Event Details
        event_title: str = SchemaField(description="Title of the event")
        location: str | None = SchemaField(
            description="Location of the event", default=None
        )
        description: str | None = SchemaField(
            description="Description of the event", default=None
        )

        # Timing
        timing: ExactTiming | DurationTiming = SchemaField(
            discriminator="discriminator",
            advanced=False,
            description="Specify when the event starts and ends",
            default_factory=lambda: DurationTiming(
                discriminator="duration_timing",
                start_datetime=datetime.now().replace(microsecond=0, second=0, minute=0)
                + timedelta(hours=1),
                duration_minutes=60,
            ),
        )

        # Calendar selection
        calendar_id: str = SchemaField(
            description="Calendar ID (use 'primary' for your main calendar)",
            default="primary",
        )

        # Guests
        guest_emails: list[str] = SchemaField(
            description="Email addresses of guests to invite", default_factory=list
        )
        send_notifications: bool = SchemaField(
            description="Send email notifications to guests", default=True
        )

        # Extras
        add_google_meet: bool = SchemaField(
            description="Include a Google Meet video conference link", default=False
        )
        recurrence: OneTimeEvent | RecurringEvent = SchemaField(
            discriminator="discriminator",
            description="Whether the event repeats",
            default_factory=lambda: OneTimeEvent(discriminator="one_time"),
        )
        reminder_minutes: list[ReminderPreset] = SchemaField(
            description="When to send reminders before the event",
            default_factory=lambda: [ReminderPreset.TEN_MINUTES],
        )

    class Output(BlockSchemaOutput):
        event_id: str = SchemaField(description="ID of the created event")
        event_link: str = SchemaField(
            description="Link to view the event in Google Calendar"
        )

    def __init__(self):
        super().__init__(
            id="ed2ec950-fbff-4204-94c0-023fb1d625e0",
            description="This block creates a new event in Google Calendar with customizable parameters.",
            categories={BlockCategory.PRODUCTIVITY},
            input_schema=GoogleCalendarCreateEventBlock.Input,
            output_schema=GoogleCalendarCreateEventBlock.Output,
            disabled=not GOOGLE_OAUTH_IS_CONFIGURED,
            test_input={
                "credentials": TEST_CREDENTIALS_INPUT,
                "event_title": "Team Meeting",
                "location": "Conference Room A",
                "description": "Weekly team sync-up",
                "calendar_id": "primary",
                "guest_emails": ["colleague1@example.com", "colleague2@example.com"],
                "add_google_meet": True,
                "send_notifications": True,
                "reminder_minutes": [
                    ReminderPreset.TEN_MINUTES.value,
                    ReminderPreset.ONE_HOUR.value,
                ],
            },
            test_credentials=TEST_CREDENTIALS,
            test_output=[
                ("event_id", "abc123event_id"),
                ("event_link", "https://calendar.google.com/calendar/event?eid=abc123"),
            ],
            test_mock={
                "_create_event": lambda *args, **kwargs: {
                    "id": "abc123event_id",
                    "htmlLink": "https://calendar.google.com/calendar/event?eid=abc123",
                }
            },
        )

    async def run(
        self, input_data: Input, *, credentials: GoogleCredentials, **kwargs
    ) -> BlockOutput:
        try:
            service = self._build_service(credentials, **kwargs)

            # Create event body
            # Get start and end times based on the timing option
            if input_data.timing.discriminator == "exact_timing":
                start_datetime = input_data.timing.start_datetime
                end_datetime = input_data.timing.end_datetime
            else:  # duration_timing
                start_datetime = input_data.timing.start_datetime
                end_datetime = start_datetime + timedelta(
                    minutes=input_data.timing.duration_minutes
                )

            # Format datetimes for Google Calendar API
            start_time_str = start_datetime.isoformat()
            end_time_str = end_datetime.isoformat()

            # Build the event body
            event_body = {
                "summary": input_data.event_title,
                "start": {"dateTime": start_time_str},
                "end": {"dateTime": end_time_str},
            }

            # Add optional fields
            if input_data.location:
                event_body["location"] = input_data.location

            if input_data.description:
                event_body["description"] = input_data.description

            # Add guests
            if input_data.guest_emails:
                event_body["attendees"] = [
                    {"email": email} for email in input_data.guest_emails
                ]

            # Add reminders
            if input_data.reminder_minutes:
                event_body["reminders"] = {
                    "useDefault": False,
                    "overrides": [
                        {"method": "popup", "minutes": reminder.value}
                        for reminder in input_data.reminder_minutes
                    ],
                }

            # Add Google Meet
            if input_data.add_google_meet:
                event_body["conferenceData"] = {
                    "createRequest": {
                        "requestId": f"meet-{uuid.uuid4()}",
                        "conferenceSolutionKey": {"type": "hangoutsMeet"},
                    }
                }

            # Add recurrence
            if input_data.recurrence.discriminator == "recurring":
                rule = f"RRULE:FREQ={input_data.recurrence.frequency.value}"
                rule += f";COUNT={input_data.recurrence.count}"
                event_body["recurrence"] = [rule]

            # Create the event
            result = await asyncio.to_thread(
                self._create_event,
                service=service,
                calendar_id=input_data.calendar_id,
                event_body=event_body,
                send_notifications=input_data.send_notifications,
                conference_data_version=1 if input_data.add_google_meet else 0,
            )

            yield "event_id", result["id"]
            yield "event_link", result["htmlLink"]

        except Exception as e:
            yield "error", str(e)

    @staticmethod
    def _build_service(credentials: GoogleCredentials, **kwargs):
        creds = Credentials(
            token=(
                credentials.access_token.get_secret_value()
                if credentials.access_token
                else None
            ),
            refresh_token=(
                credentials.refresh_token.get_secret_value()
                if credentials.refresh_token
                else None
            ),
            token_uri="https://oauth2.googleapis.com/token",
            client_id=settings.secrets.google_client_id,
            client_secret=settings.secrets.google_client_secret,
            scopes=credentials.scopes,
        )
        return build("calendar", "v3", credentials=creds)

    def _create_event(
        self,
        service,
        calendar_id: str,
        event_body: dict,
        send_notifications: bool = False,
        conference_data_version: int = 0,
    ) -> dict:
        """Create a new event in Google Calendar."""
        calendar = service.events()

        # Make the API call
        result = calendar.insert(
            calendarId=calendar_id,
            body=event_body,
            sendNotifications=send_notifications,
            conferenceDataVersion=conference_data_version,
        ).execute()

        return result
