from datetime import datetime
from unittest.mock import MagicMock, patch
from zoneinfo import ZoneInfo

import pytest
from arcade_tdk import ToolAuthorizationContext, ToolContext
from arcade_tdk.errors import RetryableToolError, ToolExecutionError
from googleapiclient.errors import HttpError

from arcade_google_calendar.enums import EventVisibility, SendUpdatesOptions
from arcade_google_calendar.tools import (
    create_event,
    delete_event,
    find_time_slots_when_everyone_is_free,
    list_calendars,
    list_events,
    update_event,
)


@pytest.fixture
def mock_context():
    mock_auth = ToolAuthorizationContext(token="fake-token")  # noqa: S106
    return ToolContext(authorization=mock_auth)


@pytest.mark.asyncio
@patch("arcade_google_calendar.tools.calendar.build_calendar_service")
async def test_list_calendars(mock_build_calendar_service, mock_context):
    mock_service = MagicMock()
    mock_build_calendar_service.return_value = mock_service

    expected_api_response = {
        "etag": '"p33for2n0pvc8o0o"',
        "items": [
            {
                "accessRole": "reader",
                "backgroundColor": "#16a765",
                "colorId": "8",
                "conferenceProperties": {"allowedConferenceSolutionTypes": ["hangoutsMeet"]},
                "defaultReminders": [],
                "description": "Holidays and Observances in Brazil",
                "etag": '"2347287866334000"',
                "foregroundColor": "#000000",
                "id": "en.brazilian#holiday@group.v.calendar.google.com",
                "kind": "calendar#calendarListEntry",
                "selected": True,
                "summary": "Holidays in Brazil",
                "timeZone": "America/Sao_Paulo",
            },
            {
                "accessRole": "owner",
                "backgroundColor": "#9fe1e7",
                "colorId": "14",
                "conferenceProperties": {"allowedConferenceSolutionTypes": ["hangoutsMeet"]},
                "defaultReminders": [{"method": "popup", "minutes": 10}],
                "etag": '"1743169667849567"',
                "foregroundColor": "#000000",
                "id": "example@arcade.dev",
                "kind": "calendar#calendarListEntry",
                "notificationSettings": {
                    "notifications": [
                        {"method": "email", "type": "eventCreation"},
                        {"method": "email", "type": "eventChange"},
                        {"method": "email", "type": "eventCancellation"},
                        {"method": "email", "type": "eventResponse"},
                    ]
                },
                "primary": True,
                "selected": True,
                "summary": "example@arcade.dev",
                "timeZone": "America/Sao_Paulo",
            },
        ],
        "kind": "calendar#calendarList",
        "nextSyncToken": "XkJ8Hy5mN2pQvL9sR4tW7cA3fE1iU6nB",
    }

    expected_tool_response = {
        "num_calendars": 2,
        "calendars": [
            {
                "description": "Holidays and Observances in Brazil",
                "id": "en.brazilian#holiday@group.v.calendar.google.com",
                "summary": "Holidays in Brazil",
                "timeZone": "America/Sao_Paulo",
            },
            {
                "id": "example@arcade.dev",
                "summary": "example@arcade.dev",
                "timeZone": "America/Sao_Paulo",
            },
        ],
        "next_page_token": None,
    }

    mock_service.calendarList().list().execute.return_value = expected_api_response

    response = await list_calendars(context=mock_context)
    assert response == expected_tool_response

    # Case: HttpError during calendars listing
    mock_service.calendarList().list().execute.side_effect = HttpError(
        resp=MagicMock(status=400),
        content=b'{"error": {"message": "Invalid request"}}',
    )

    with pytest.raises(ToolExecutionError):
        await list_calendars(context=mock_context)


@pytest.mark.asyncio
@patch("arcade_google_calendar.tools.calendar.build_calendar_service")
async def test_create_event(mock_build, mock_context):
    mock_service = MagicMock()
    mock_build.return_value = mock_service

    # Mock the calendar's time zone
    mock_service.calendars().get().execute.return_value = {"timeZone": "America/Los_Angeles"}

    # Case: HttpError during event creation
    mock_service.events().insert().execute.side_effect = HttpError(
        resp=MagicMock(status=400),
        content=b'{"error": {"message": "Invalid request"}}',
    )

    with pytest.raises(ToolExecutionError):
        await create_event(
            context=mock_context,
            summary="Test Event",
            start_datetime="2024-12-31T15:30:00",
            end_datetime="2024-12-31T17:30:00",
            description="Test Description",
            location="Test Location",
            visibility=EventVisibility.PRIVATE,
            attendee_emails=["test@example.com"],
        )


@pytest.mark.asyncio
@patch("arcade_google_calendar.tools.calendar.build_calendar_service")
async def test_list_events(mock_build, mock_context):
    mock_service = MagicMock()
    mock_build.return_value = mock_service
    # Mock the calendar's time zone
    mock_service.calendars().get().execute.return_value = {"timeZone": "America/Los_Angeles"}

    # Mock the events list response
    mock_events_list_response = {
        "items": [
            {
                "creator": {"email": "example@arcade-ai.com", "self": True},
                "end": {"dateTime": "2024-09-27T01:00:00-07:00", "timeZone": "America/Los_Angeles"},
                "eventType": "default",
                "htmlLink": "https://www.google.com/calendar/event?eid=event1",
                "id": "event1",
                "organizer": {"email": "example@arcade-ai.com", "self": True},
                "start": {
                    "dateTime": "2024-09-27T00:00:00-07:00",
                    "timeZone": "America/Los_Angeles",
                },
                "summary": "Event 1",
            },
            {
                "creator": {"email": "example@arcade-ai.com", "self": True},
                "end": {"dateTime": "2024-09-27T17:00:00-07:00", "timeZone": "America/Los_Angeles"},
                "eventType": "default",
                "htmlLink": "https://www.google.com/calendar/event?eid=event2",
                "id": "event2",
                "organizer": {"email": "example@arcade-ai.com", "self": True},
                "start": {
                    "dateTime": "2024-09-27T14:00:00-07:00",
                    "timeZone": "America/Los_Angeles",
                },
                "summary": "Event 2",
            },
        ]
    }
    expected_tool_response = {
        "events_count": len(mock_events_list_response["items"]),
        "events": mock_events_list_response["items"],
    }
    mock_service.events().list().execute.return_value = mock_events_list_response
    response = await list_events(
        context=mock_context,
        min_end_datetime="2024-09-15T09:00:00",
        max_start_datetime="2024-09-16T17:00:00",
    )
    assert response == expected_tool_response

    # Case: HttpError during events listing
    mock_service.events().list().execute.side_effect = HttpError(
        resp=MagicMock(status=400),
        content=b'{"error": {"message": "Invalid request"}}',
    )

    with pytest.raises(ToolExecutionError):
        await list_events(
            context=mock_context,
            min_end_datetime="2024-09-15T09:00:00",
            max_start_datetime="2024-09-16T17:00:00",
        )


@pytest.mark.asyncio
@patch("arcade_google_calendar.tools.calendar.build_calendar_service")
async def test_update_event(mock_build, mock_context):
    mock_service = MagicMock()
    mock_build.return_value = mock_service

    # Mock retrieval of the event
    mock_service.events().get().execute.side_effect = HttpError(
        resp=MagicMock(status=404),
        content=b'{"error": {"message": "Event not found"}}',
    )

    with pytest.raises(ToolExecutionError):
        await update_event(
            context=mock_context,
            event_id="1234567890",
            updated_start_datetime="2024-12-31T00:15:00",
            updated_end_datetime="2024-12-31T01:15:00",
            updated_summary="Updated Event",
            updated_description="Updated Description",
            updated_location="Updated Location",
            updated_visibility=EventVisibility.PRIVATE,
            attendee_emails_to_add=["test@example.com"],
            attendee_emails_to_remove=["test@example2.com"],
            send_updates=SendUpdatesOptions.ALL,
        )


@pytest.mark.asyncio
@patch("arcade_google_calendar.tools.calendar.build_calendar_service")
async def test_delete_event(mock_build, mock_context):
    mock_service = MagicMock()
    mock_build.return_value = mock_service
    mock_service.events().delete().execute.side_effect = HttpError(
        resp=MagicMock(status=404),
        content=b'{"error": {"message": "Event not found"}}',
    )

    with pytest.raises(ToolExecutionError):
        await delete_event(
            context=mock_context,
            event_id="nonexistent_event",
            send_updates=SendUpdatesOptions.ALL,
        )


@pytest.mark.asyncio
@patch("arcade_google_calendar.utils.get_now")
@patch("arcade_google_calendar.tools.calendar.build_oauth_service")
@patch("arcade_google_calendar.tools.calendar.build_calendar_service")
async def test_find_free_slots_happiest_path_single_user(
    mock_build_calendar_service, mock_build_oauth_service, mock_get_now, mock_context
):
    calendar_service = MagicMock()
    oauth_service = MagicMock()

    mock_get_now.return_value = datetime(
        2025, 3, 10, 9, 25, 0, tzinfo=ZoneInfo("America/Los_Angeles")
    )
    mock_build_oauth_service.return_value = oauth_service
    mock_build_calendar_service.return_value = calendar_service

    oauth_service.userinfo().get().execute.return_value = {
        "email": "example@arcade.dev",
    }

    calendar_service.freebusy().query().execute.return_value = {
        "calendars": {
            "example@arcade.dev": {"busy": []},
        }
    }

    calendar_service.calendars().get().execute.return_value = {
        "timeZone": "America/Los_Angeles",
    }

    response = await find_time_slots_when_everyone_is_free(
        context=mock_context,
        email_addresses=["example@arcade.dev"],
        start_date="2025-03-10",
        end_date="2025-03-11",
        start_time_boundary="08:00",
        end_time_boundary="18:00",
    )

    assert response == {
        "free_slots": [
            {
                "start": {
                    "datetime": "2025-03-10T09:25:00-07:00",
                    "weekday": "Monday",
                },
                "end": {
                    "datetime": "2025-03-10T18:00:00-07:00",
                    "weekday": "Monday",
                },
            },
            {
                "start": {
                    "datetime": "2025-03-11T08:00:00-07:00",
                    "weekday": "Tuesday",
                },
                "end": {
                    "datetime": "2025-03-11T18:00:00-07:00",
                    "weekday": "Tuesday",
                },
            },
        ],
        "timezone": "America/Los_Angeles",
    }


@pytest.mark.asyncio
@patch("arcade_google_calendar.utils.get_now")
@patch("arcade_google_calendar.tools.calendar.build_oauth_service")
@patch("arcade_google_calendar.tools.calendar.build_calendar_service")
async def test_find_free_slots_happiest_path_single_user_with_busy_times(
    mock_build_calendar_service, mock_build_oauth_service, mock_get_now, mock_context
):
    calendar_service = MagicMock()
    oauth_service = MagicMock()

    mock_get_now.return_value = datetime(
        2025, 3, 10, 9, 25, 0, tzinfo=ZoneInfo("America/Los_Angeles")
    )

    mock_build_oauth_service.return_value = oauth_service
    mock_build_calendar_service.return_value = calendar_service

    oauth_service.userinfo().get().execute.return_value = {
        "email": "example@arcade.dev",
    }

    calendar_service.freebusy().query().execute.return_value = {
        "calendars": {
            "example@arcade.dev": {
                "busy": [
                    {
                        "start": "2025-03-10T11:00:00-07:00",
                        "end": "2025-03-10T12:00:00-07:00",
                    },
                    {
                        "start": "2025-03-10T14:15:00-07:00",
                        "end": "2025-03-10T14:30:00-07:00",
                    },
                ]
            },
        }
    }

    calendar_service.calendars().get().execute.return_value = {
        "timeZone": "America/Los_Angeles",
    }

    response = await find_time_slots_when_everyone_is_free(
        context=mock_context,
        email_addresses=["example@arcade.dev"],
        start_date="2025-03-10",
        end_date="2025-03-11",
        start_time_boundary="08:00",
        end_time_boundary="18:00",
    )

    assert response == {
        "free_slots": [
            {
                "start": {
                    "datetime": "2025-03-10T09:25:00-07:00",
                    "weekday": "Monday",
                },
                "end": {
                    "datetime": "2025-03-10T11:00:00-07:00",
                    "weekday": "Monday",
                },
            },
            {
                "start": {
                    "datetime": "2025-03-10T12:00:00-07:00",
                    "weekday": "Monday",
                },
                "end": {
                    "datetime": "2025-03-10T14:15:00-07:00",
                    "weekday": "Monday",
                },
            },
            {
                "start": {
                    "datetime": "2025-03-10T14:30:00-07:00",
                    "weekday": "Monday",
                },
                "end": {
                    "datetime": "2025-03-10T18:00:00-07:00",
                    "weekday": "Monday",
                },
            },
            {
                "start": {
                    "datetime": "2025-03-11T08:00:00-07:00",
                    "weekday": "Tuesday",
                },
                "end": {
                    "datetime": "2025-03-11T18:00:00-07:00",
                    "weekday": "Tuesday",
                },
            },
        ],
        "timezone": "America/Los_Angeles",
    }


@pytest.mark.asyncio
@patch("arcade_google_calendar.utils.get_now")
@patch("arcade_google_calendar.tools.calendar.build_oauth_service")
@patch("arcade_google_calendar.tools.calendar.build_calendar_service")
async def test_find_free_slots_happiest_path_multiple_users_with_busy_times(
    mock_build_calendar_service, mock_build_oauth_service, mock_get_now, mock_context
):
    calendar_service = MagicMock()
    oauth_service = MagicMock()

    mock_get_now.return_value = datetime(
        2025, 3, 10, 9, 25, 0, tzinfo=ZoneInfo("America/Los_Angeles")
    )

    mock_build_oauth_service.return_value = oauth_service
    mock_build_calendar_service.return_value = calendar_service

    oauth_service.userinfo().get().execute.return_value = {
        "email": "example@arcade.dev",
    }

    calendar_service.freebusy().query().execute.return_value = {
        "calendars": {
            "example@arcade.dev": {
                "busy": [
                    {
                        "start": "2025-03-10T11:00:00-07:00",
                        "end": "2025-03-10T12:00:00-07:00",
                    },
                    {
                        "start": "2025-03-10T14:15:00-07:00",
                        "end": "2025-03-10T14:30:00-07:00",
                    },
                ]
            },
            "example2@arcade.dev": {
                "busy": [
                    {
                        "start": "2025-03-10T11:30:00-07:00",
                        "end": "2025-03-10T12:45:00-07:00",
                    },
                    {
                        "start": "2025-03-11T06:00:00-07:00",
                        "end": "2025-03-11T07:00:00-07:00",
                    },
                ]
            },
        }
    }

    calendar_service.calendars().get().execute.return_value = {
        "timeZone": "America/Los_Angeles",
    }

    response = await find_time_slots_when_everyone_is_free(
        context=mock_context,
        email_addresses=["example@arcade.dev", "example2@arcade.dev"],
        start_date="2025-03-10",
        end_date="2025-03-11",
        start_time_boundary="08:00",
        end_time_boundary="18:00",
    )

    assert response == {
        "free_slots": [
            {
                "start": {
                    "datetime": "2025-03-10T09:25:00-07:00",
                    "weekday": "Monday",
                },
                "end": {
                    "datetime": "2025-03-10T11:00:00-07:00",
                    "weekday": "Monday",
                },
            },
            {
                "start": {
                    "datetime": "2025-03-10T12:45:00-07:00",
                    "weekday": "Monday",
                },
                "end": {
                    "datetime": "2025-03-10T14:15:00-07:00",
                    "weekday": "Monday",
                },
            },
            {
                "start": {
                    "datetime": "2025-03-10T14:30:00-07:00",
                    "weekday": "Monday",
                },
                "end": {
                    "datetime": "2025-03-10T18:00:00-07:00",
                    "weekday": "Monday",
                },
            },
            {
                "start": {
                    "datetime": "2025-03-11T08:00:00-07:00",
                    "weekday": "Tuesday",
                },
                "end": {
                    "datetime": "2025-03-11T18:00:00-07:00",
                    "weekday": "Tuesday",
                },
            },
        ],
        "timezone": "America/Los_Angeles",
    }


@pytest.mark.asyncio
@patch("arcade_google_calendar.utils.get_now")
@patch("arcade_google_calendar.tools.calendar.build_oauth_service")
@patch("arcade_google_calendar.tools.calendar.build_calendar_service")
async def test_find_free_slots_with_google_calendar_error_not_found(
    mock_build_calendar_service, mock_build_oauth_service, mock_get_now, mock_context
):
    calendar_service = MagicMock()
    oauth_service = MagicMock()

    mock_get_now.return_value = datetime(
        2025, 3, 10, 9, 25, 0, tzinfo=ZoneInfo("America/Los_Angeles")
    )
    mock_build_oauth_service.return_value = oauth_service
    mock_build_calendar_service.return_value = calendar_service

    oauth_service.userinfo().get().execute.return_value = {
        "email": "example@arcade.dev",
    }

    calendar_service.freebusy().query().execute.return_value = {
        "calendars": {
            "example@arcade.dev": {
                "busy": [
                    {
                        "start": "2025-03-10T11:00:00-07:00",
                        "end": "2025-03-10T12:00:00-07:00",
                    },
                    {
                        "start": "2025-03-10T14:15:00-07:00",
                        "end": "2025-03-10T14:30:00-07:00",
                    },
                ]
            },
            "example2@arcade.dev": {
                "errors": [
                    {
                        "reason": "notFound",
                        "domain": "calendar",
                    }
                ]
            },
        }
    }

    calendar_service.calendars().get().execute.return_value = {
        "timeZone": "America/Los_Angeles",
    }

    with pytest.raises(RetryableToolError):
        await find_time_slots_when_everyone_is_free(
            context=mock_context,
            email_addresses=["example@arcade.dev", "example2@arcade.dev"],
            start_date="2025-03-10",
            end_date="2025-03-11",
            start_time_boundary="08:00",
            end_time_boundary="18:00",
        )
