"""Tests for calendar service."""

from __future__ import annotations

from datetime import UTC, datetime, timedelta
from typing import TYPE_CHECKING
from unittest.mock import AsyncMock, MagicMock
from uuid import uuid4

import pytest

from noteflow.domain.entities import Integration, IntegrationStatus, IntegrationType
from noteflow.domain.ports.calendar import CalendarEventInfo
from noteflow.domain.value_objects import OAuthTokens

if TYPE_CHECKING:
    from noteflow.config.settings import CalendarIntegrationSettings


@pytest.fixture
def mock_oauth_manager() -> MagicMock:
    """Create mock OAuth manager."""
    manager = MagicMock()
    manager.initiate_auth.return_value = ("https://auth.example.com", "state-123")
    manager.complete_auth = AsyncMock(
        return_value=OAuthTokens(
            access_token="access-token",
            refresh_token="refresh-token",
            token_type="Bearer",
            expires_at=datetime.now(UTC) + timedelta(hours=1),
            scope="email calendar",
        )
    )
    manager.refresh_tokens = AsyncMock(
        return_value=OAuthTokens(
            access_token="new-access-token",
            refresh_token="refresh-token",
            token_type="Bearer",
            expires_at=datetime.now(UTC) + timedelta(hours=1),
            scope="email calendar",
        )
    )
    manager.revoke_tokens = AsyncMock()
    return manager


@pytest.fixture
def mock_google_adapter() -> MagicMock:
    """Create mock Google Calendar adapter."""
    adapter = MagicMock()
    adapter.list_events = AsyncMock(
        return_value=[
            CalendarEventInfo(
                id="event-1",
                title="Test Meeting",
                start_time=datetime.now(UTC) + timedelta(hours=1),
                end_time=datetime.now(UTC) + timedelta(hours=2),
                attendees=("alice@example.com",),
                provider="google",
            )
        ]
    )
    adapter.get_user_email = AsyncMock(return_value="user@gmail.com")
    return adapter


@pytest.fixture
def mock_outlook_adapter() -> MagicMock:
    """Create mock Outlook Calendar adapter."""
    adapter = MagicMock()
    adapter.list_events = AsyncMock(return_value=[])
    adapter.get_user_email = AsyncMock(return_value="user@outlook.com")
    return adapter


@pytest.fixture
def calendar_mock_uow(mock_uow: MagicMock) -> MagicMock:
    """Configure mock_uow with calendar service specific integrations behavior."""
    mock_uow.integrations.get_by_type_and_provider = AsyncMock(return_value=None)
    mock_uow.integrations.add = AsyncMock()
    mock_uow.integrations.get_secrets = AsyncMock(return_value=None)
    mock_uow.integrations.set_secrets = AsyncMock()
    return mock_uow


class TestCalendarServiceInitiateOAuth:
    """Tests for CalendarService.initiate_oauth."""

    @pytest.mark.asyncio
    async def test_initiate_oauth_returns_auth_url_and_state(
        self,
        calendar_settings: CalendarIntegrationSettings,
        mock_oauth_manager: MagicMock,
        mock_google_adapter: MagicMock,
        mock_outlook_adapter: MagicMock,
        calendar_mock_uow: MagicMock,
    ) -> None:
        """initiate_oauth should return auth URL and state."""
        from noteflow.application.services.calendar_service import CalendarService

        service = CalendarService(
            uow_factory=lambda: calendar_mock_uow,
            settings=calendar_settings,
            oauth_manager=mock_oauth_manager,
            google_adapter=mock_google_adapter,
            outlook_adapter=mock_outlook_adapter,
        )

        auth_url, state = await service.initiate_oauth("google", "http://localhost/callback")

        assert auth_url == "https://auth.example.com"
        assert state == "state-123"
        mock_oauth_manager.initiate_auth.assert_called_once()


class TestCalendarServiceCompleteOAuth:
    """Tests for CalendarService.complete_oauth."""

    @pytest.mark.asyncio
    async def test_complete_oauth_stores_tokens(
        self,
        calendar_settings: CalendarIntegrationSettings,
        mock_oauth_manager: MagicMock,
        mock_google_adapter: MagicMock,
        mock_outlook_adapter: MagicMock,
        calendar_mock_uow: MagicMock,
    ) -> None:
        """complete_oauth should store tokens in integration secrets."""
        from noteflow.application.services.calendar_service import CalendarService

        calendar_mock_uow.integrations.get_by_provider = AsyncMock(return_value=None)
        calendar_mock_uow.integrations.create = AsyncMock()
        calendar_mock_uow.integrations.update = AsyncMock()

        service = CalendarService(
            uow_factory=lambda: calendar_mock_uow,
            settings=calendar_settings,
            oauth_manager=mock_oauth_manager,
            google_adapter=mock_google_adapter,
            outlook_adapter=mock_outlook_adapter,
        )

        result = await service.complete_oauth("google", "auth-code", "state-123")

        assert result is True
        mock_oauth_manager.complete_auth.assert_called_once()
        calendar_mock_uow.integrations.set_secrets.assert_called_once()
        calendar_mock_uow.commit.assert_called()

    @pytest.mark.asyncio
    async def test_complete_oauth_creates_integration_if_not_exists(
        self,
        calendar_settings: CalendarIntegrationSettings,
        mock_oauth_manager: MagicMock,
        mock_google_adapter: MagicMock,
        mock_outlook_adapter: MagicMock,
        calendar_mock_uow: MagicMock,
    ) -> None:
        """complete_oauth should create new integration if none exists."""
        from noteflow.application.services.calendar_service import CalendarService

        calendar_mock_uow.integrations.get_by_provider = AsyncMock(return_value=None)
        calendar_mock_uow.integrations.create = AsyncMock()
        calendar_mock_uow.integrations.update = AsyncMock()

        service = CalendarService(
            uow_factory=lambda: calendar_mock_uow,
            settings=calendar_settings,
            oauth_manager=mock_oauth_manager,
            google_adapter=mock_google_adapter,
            outlook_adapter=mock_outlook_adapter,
        )

        await service.complete_oauth("google", "auth-code", "state-123")

        calendar_mock_uow.integrations.create.assert_called_once()

    @pytest.mark.asyncio
    async def test_complete_oauth_updates_existing_integration(
        self,
        calendar_settings: CalendarIntegrationSettings,
        mock_oauth_manager: MagicMock,
        mock_google_adapter: MagicMock,
        mock_outlook_adapter: MagicMock,
        calendar_mock_uow: MagicMock,
    ) -> None:
        """complete_oauth should update existing integration."""
        from noteflow.application.services.calendar_service import CalendarService

        existing_integration = Integration.create(
            workspace_id=uuid4(),
            name="Google Calendar",
            integration_type=IntegrationType.CALENDAR,
            config={"provider": "google"},
        )
        calendar_mock_uow.integrations.get_by_provider = AsyncMock(return_value=existing_integration)
        calendar_mock_uow.integrations.create = AsyncMock()
        calendar_mock_uow.integrations.update = AsyncMock()

        service = CalendarService(
            uow_factory=lambda: calendar_mock_uow,
            settings=calendar_settings,
            oauth_manager=mock_oauth_manager,
            google_adapter=mock_google_adapter,
            outlook_adapter=mock_outlook_adapter,
        )

        await service.complete_oauth("google", "auth-code", "state-123")

        calendar_mock_uow.integrations.create.assert_not_called()
        assert existing_integration.status == IntegrationStatus.CONNECTED


class TestCalendarServiceGetConnectionStatus:
    """Tests for CalendarService.get_connection_status."""

    @pytest.mark.asyncio
    async def test_get_connection_status_returns_connected_info(
        self,
        calendar_settings: CalendarIntegrationSettings,
        mock_oauth_manager: MagicMock,
        mock_google_adapter: MagicMock,
        mock_outlook_adapter: MagicMock,
        calendar_mock_uow: MagicMock,
    ) -> None:
        """get_connection_status should return connection info for connected provider."""
        from noteflow.application.services.calendar_service import CalendarService

        integration = Integration.create(
            workspace_id=uuid4(),
            name="Google Calendar",
            integration_type=IntegrationType.CALENDAR,
            config={"provider": "google"},
        )
        integration.connect(provider_email="user@gmail.com")
        calendar_mock_uow.integrations.get_by_provider = AsyncMock(return_value=integration)
        calendar_mock_uow.integrations.get_secrets = AsyncMock(return_value={
            "access_token": "token",
            "refresh_token": "refresh",
            "token_type": "Bearer",
            "expires_at": (datetime.now(UTC) + timedelta(hours=1)).isoformat(),
            "scope": "calendar",
        })

        service = CalendarService(
            uow_factory=lambda: calendar_mock_uow,
            settings=calendar_settings,
            oauth_manager=mock_oauth_manager,
            google_adapter=mock_google_adapter,
            outlook_adapter=mock_outlook_adapter,
        )

        status = await service.get_connection_status("google")

        assert status.status == "connected"
        assert status.provider == "google"

    @pytest.mark.asyncio
    async def test_get_connection_status_returns_disconnected_when_no_integration(
        self,
        calendar_settings: CalendarIntegrationSettings,
        mock_oauth_manager: MagicMock,
        mock_google_adapter: MagicMock,
        mock_outlook_adapter: MagicMock,
        calendar_mock_uow: MagicMock,
    ) -> None:
        """get_connection_status should return disconnected when no integration."""
        from noteflow.application.services.calendar_service import CalendarService

        calendar_mock_uow.integrations.get_by_provider = AsyncMock(return_value=None)

        service = CalendarService(
            uow_factory=lambda: calendar_mock_uow,
            settings=calendar_settings,
            oauth_manager=mock_oauth_manager,
            google_adapter=mock_google_adapter,
            outlook_adapter=mock_outlook_adapter,
        )

        status = await service.get_connection_status("google")

        assert status.status == "disconnected"


class TestCalendarServiceDisconnect:
    """Tests for CalendarService.disconnect."""

    @pytest.mark.asyncio
    async def test_disconnect_revokes_tokens_and_deletes_integration(
        self,
        calendar_settings: CalendarIntegrationSettings,
        mock_oauth_manager: MagicMock,
        mock_google_adapter: MagicMock,
        mock_outlook_adapter: MagicMock,
        calendar_mock_uow: MagicMock,
    ) -> None:
        """disconnect should revoke tokens and delete integration."""
        from noteflow.application.services.calendar_service import CalendarService

        integration = Integration.create(
            workspace_id=uuid4(),
            name="Google Calendar",
            integration_type=IntegrationType.CALENDAR,
            config={"provider": "google"},
        )
        integration.connect(provider_email="user@gmail.com")
        calendar_mock_uow.integrations.get_by_provider = AsyncMock(return_value=integration)
        calendar_mock_uow.integrations.get_secrets = AsyncMock(return_value={"access_token": "token"})
        calendar_mock_uow.integrations.delete = AsyncMock()

        service = CalendarService(
            uow_factory=lambda: calendar_mock_uow,
            settings=calendar_settings,
            oauth_manager=mock_oauth_manager,
            google_adapter=mock_google_adapter,
            outlook_adapter=mock_outlook_adapter,
        )

        result = await service.disconnect("google")

        assert result is True
        mock_oauth_manager.revoke_tokens.assert_called_once()
        calendar_mock_uow.integrations.delete.assert_called_once()
        calendar_mock_uow.commit.assert_called()


class TestCalendarServiceListEvents:
    """Tests for CalendarService.list_calendar_events."""

    @pytest.mark.asyncio
    async def test_list_events_fetches_from_connected_provider(
        self,
        calendar_settings: CalendarIntegrationSettings,
        mock_oauth_manager: MagicMock,
        mock_google_adapter: MagicMock,
        mock_outlook_adapter: MagicMock,
        calendar_mock_uow: MagicMock,
    ) -> None:
        """list_calendar_events should fetch events from connected provider."""
        from noteflow.application.services.calendar_service import CalendarService

        integration = Integration.create(
            workspace_id=uuid4(),
            name="Google Calendar",
            integration_type=IntegrationType.CALENDAR,
            config={"provider": "google"},
        )
        integration.connect(provider_email="user@gmail.com")
        calendar_mock_uow.integrations.get_by_provider = AsyncMock(return_value=integration)
        calendar_mock_uow.integrations.get_secrets = AsyncMock(return_value={
            "access_token": "token",
            "refresh_token": "refresh",
            "token_type": "Bearer",
            "expires_at": (datetime.now(UTC) + timedelta(hours=1)).isoformat(),
            "scope": "calendar",
        })
        calendar_mock_uow.integrations.update = AsyncMock()

        service = CalendarService(
            uow_factory=lambda: calendar_mock_uow,
            settings=calendar_settings,
            oauth_manager=mock_oauth_manager,
            google_adapter=mock_google_adapter,
            outlook_adapter=mock_outlook_adapter,
        )

        events = await service.list_calendar_events(provider="google")

        assert len(events) == 1
        assert events[0].title == "Test Meeting"
        mock_google_adapter.list_events.assert_called_once()

    @pytest.mark.asyncio
    async def test_list_events_refreshes_expired_token(
        self,
        calendar_settings: CalendarIntegrationSettings,
        mock_oauth_manager: MagicMock,
        mock_google_adapter: MagicMock,
        mock_outlook_adapter: MagicMock,
        calendar_mock_uow: MagicMock,
    ) -> None:
        """list_calendar_events should refresh expired token before fetching."""
        from noteflow.application.services.calendar_service import CalendarService

        integration = Integration.create(
            workspace_id=uuid4(),
            name="Google Calendar",
            integration_type=IntegrationType.CALENDAR,
            config={"provider": "google"},
        )
        integration.connect(provider_email="user@gmail.com")
        calendar_mock_uow.integrations.get_by_provider = AsyncMock(return_value=integration)
        calendar_mock_uow.integrations.get_secrets = AsyncMock(return_value={
            "access_token": "expired-token",
            "refresh_token": "refresh-token",
            "token_type": "Bearer",
            "expires_at": (datetime.now(UTC) - timedelta(hours=1)).isoformat(),
            "scope": "calendar",
        })
        calendar_mock_uow.integrations.update = AsyncMock()

        service = CalendarService(
            uow_factory=lambda: calendar_mock_uow,
            settings=calendar_settings,
            oauth_manager=mock_oauth_manager,
            google_adapter=mock_google_adapter,
            outlook_adapter=mock_outlook_adapter,
        )

        await service.list_calendar_events(provider="google")

        mock_oauth_manager.refresh_tokens.assert_called_once()
        calendar_mock_uow.integrations.set_secrets.assert_called()

    @pytest.mark.asyncio
    async def test_list_events_raises_when_not_connected(
        self,
        calendar_settings: CalendarIntegrationSettings,
        mock_oauth_manager: MagicMock,
        mock_google_adapter: MagicMock,
        mock_outlook_adapter: MagicMock,
        calendar_mock_uow: MagicMock,
    ) -> None:
        """list_calendar_events should raise error when provider not connected."""
        from noteflow.application.services.calendar_service import (
            CalendarService,
            CalendarServiceError,
        )

        calendar_mock_uow.integrations.get_by_provider = AsyncMock(return_value=None)

        service = CalendarService(
            uow_factory=lambda: calendar_mock_uow,
            settings=calendar_settings,
            oauth_manager=mock_oauth_manager,
            google_adapter=mock_google_adapter,
            outlook_adapter=mock_outlook_adapter,
        )

        with pytest.raises(CalendarServiceError, match="not connected"):
            await service.list_calendar_events(provider="google")
