"""Calendar integration service.

Orchestrates OAuth flow, token management, and calendar event fetching.
Uses existing Integration entity and IntegrationRepository for persistence.
"""

from __future__ import annotations

from typing import TYPE_CHECKING
from uuid import UUID

from noteflow.config.constants import ERR_TOKEN_REFRESH_PREFIX, OAUTH_FIELD_ACCESS_TOKEN
from noteflow.domain.entities.integration import Integration, IntegrationStatus, IntegrationType
from noteflow.domain.ports.calendar import CalendarEventInfo, OAuthConnectionInfo
from noteflow.domain.value_objects import OAuthProvider, OAuthTokens
from noteflow.infrastructure.calendar import (
    GoogleCalendarAdapter,
    OAuthManager,
    OutlookCalendarAdapter,
)
from noteflow.infrastructure.calendar.google_adapter import GoogleCalendarError
from noteflow.infrastructure.calendar.oauth_manager import OAuthError
from noteflow.infrastructure.calendar.outlook_adapter import OutlookCalendarError
from noteflow.infrastructure.logging import get_logger

if TYPE_CHECKING:
    from collections.abc import Callable

    from noteflow.config.settings import CalendarIntegrationSettings
    from noteflow.domain.ports.unit_of_work import UnitOfWork

logger = get_logger(__name__)


class CalendarServiceError(Exception):
    """Calendar service operation failed."""


class CalendarService:
    """Calendar integration service.

    Orchestrates OAuth flow and calendar event fetching. Uses:
    - IntegrationRepository for Integration entity CRUD
    - IntegrationRepository.get_secrets/set_secrets for encrypted token storage
    - OAuthManager for PKCE OAuth flow
    - GoogleCalendarAdapter/OutlookCalendarAdapter for provider APIs
    """

    # Default workspace ID for single-user mode
    DEFAULT_WORKSPACE_ID = UUID("00000000-0000-0000-0000-000000000001")

    def __init__(
        self,
        uow_factory: Callable[[], UnitOfWork],
        settings: CalendarIntegrationSettings,
        oauth_manager: OAuthManager | None = None,
        google_adapter: GoogleCalendarAdapter | None = None,
        outlook_adapter: OutlookCalendarAdapter | None = None,
    ) -> None:
        """Initialize calendar service.

        Args:
            uow_factory: Factory function returning UnitOfWork instances.
            settings: Calendar settings with OAuth credentials.
            oauth_manager: Optional OAuth manager (created from settings if not provided).
            google_adapter: Optional Google adapter (created if not provided).
            outlook_adapter: Optional Outlook adapter (created if not provided).
        """
        self._uow_factory = uow_factory
        self._settings = settings
        self._oauth_manager = oauth_manager or OAuthManager(settings)
        self._google_adapter = google_adapter or GoogleCalendarAdapter()
        self._outlook_adapter = outlook_adapter or OutlookCalendarAdapter()

    async def initiate_oauth(
        self,
        provider: str,
        redirect_uri: str | None = None,
    ) -> tuple[str, str]:
        """Start OAuth flow for a calendar provider.

        Args:
            provider: Provider name ('google' or 'outlook').
            redirect_uri: Optional override for OAuth callback URI.

        Returns:
            Tuple of (authorization_url, state_token).

        Raises:
            CalendarServiceError: If provider is invalid or credentials not configured.
        """
        oauth_provider = self._parse_provider(provider)
        effective_redirect = redirect_uri or self._settings.redirect_uri

        try:
            auth_url, state = self._oauth_manager.initiate_auth(
                provider=oauth_provider,
                redirect_uri=effective_redirect,
            )
            logger.info("Initiated OAuth flow for provider=%s", provider)
            return auth_url, state
        except OAuthError as e:
            raise CalendarServiceError(str(e)) from e

    async def complete_oauth(
        self,
        provider: str,
        code: str,
        state: str,
    ) -> bool:
        """Complete OAuth flow and store tokens.

        Creates or updates Integration entity with CALENDAR type and
        stores encrypted tokens via IntegrationRepository.set_secrets.

        Args:
            provider: Provider name ('google' or 'outlook').
            code: Authorization code from OAuth callback.
            state: State parameter from OAuth callback.

        Returns:
            True if OAuth completed successfully.

        Raises:
            CalendarServiceError: If OAuth exchange fails.
        """
        oauth_provider = self._parse_provider(provider)

        try:
            tokens = await self._oauth_manager.complete_auth(
                provider=oauth_provider,
                code=code,
                state=state,
            )
        except OAuthError as e:
            raise CalendarServiceError(f"OAuth failed: {e}") from e

        # Get user email from provider
        try:
            email = await self._fetch_account_email(oauth_provider, tokens.access_token)
        except (GoogleCalendarError, OutlookCalendarError) as e:
            raise CalendarServiceError(f"Failed to get user email: {e}") from e

        # Persist integration and tokens
        async with self._uow_factory() as uow:
            integration = await uow.integrations.get_by_provider(
                provider=provider,
                integration_type=IntegrationType.CALENDAR.value,
            )

            if integration is None:
                integration = Integration.create(
                    workspace_id=self.DEFAULT_WORKSPACE_ID,
                    name=f"{provider.title()} Calendar",
                    integration_type=IntegrationType.CALENDAR,
                    config={"provider": provider},
                )
                await uow.integrations.create(integration)
            else:
                integration.config["provider"] = provider

            integration.connect(provider_email=email)
            await uow.integrations.update(integration)

            # Store encrypted tokens
            await uow.integrations.set_secrets(
                integration_id=integration.id,
                secrets=tokens.to_secrets_dict(),
            )
            await uow.commit()

        logger.info("Completed OAuth for provider=%s, email=%s", provider, email)
        return True

    async def get_connection_status(self, provider: str) -> OAuthConnectionInfo:
        """Get OAuth connection status for a provider.

        Args:
            provider: Provider name ('google' or 'outlook').

        Returns:
            OAuthConnectionInfo with status and details.
        """
        async with self._uow_factory() as uow:
            integration = await uow.integrations.get_by_provider(
                provider=provider,
                integration_type=IntegrationType.CALENDAR.value,
            )

            if integration is None:
                return OAuthConnectionInfo(
                    provider=provider,
                    status=IntegrationStatus.DISCONNECTED.value,
                )

            # Check token expiry
            secrets = await uow.integrations.get_secrets(integration.id)
            expires_at = None
            status = self._map_integration_status(integration.status)

            if secrets and integration.is_connected:
                try:
                    tokens = OAuthTokens.from_secrets_dict(secrets)
                    expires_at = tokens.expires_at
                    if tokens.is_expired():
                        status = "expired"
                except (KeyError, ValueError):
                    status = IntegrationStatus.ERROR.value

            return OAuthConnectionInfo(
                provider=provider,
                status=status,
                email=integration.provider_email,
                expires_at=expires_at,
                error_message=integration.error_message,
            )

    async def disconnect(self, provider: str) -> bool:
        """Disconnect OAuth integration and revoke tokens.

        Args:
            provider: Provider name ('google' or 'outlook').

        Returns:
            True if disconnected successfully.
        """
        oauth_provider = self._parse_provider(provider)

        async with self._uow_factory() as uow:
            integration = await uow.integrations.get_by_provider(
                provider=provider,
                integration_type=IntegrationType.CALENDAR.value,
            )

            if integration is None:
                return False

            # Get tokens before deletion for revocation
            secrets = await uow.integrations.get_secrets(integration.id)
            access_token = secrets.get(OAUTH_FIELD_ACCESS_TOKEN) if secrets else None

            # Delete integration (cascades to secrets)
            await uow.integrations.delete(integration.id)
            await uow.commit()

        # Revoke tokens with provider (best-effort)
        if access_token:
            try:
                await self._oauth_manager.revoke_tokens(oauth_provider, access_token)
            except OAuthError as e:
                logger.warning(
                    "Failed to revoke tokens for provider=%s: %s",
                    provider,
                    e,
                )

        logger.info("Disconnected provider=%s", provider)
        return True

    async def list_calendar_events(
        self,
        provider: str | None = None,
        hours_ahead: int | None = None,
        limit: int | None = None,
    ) -> list[CalendarEventInfo]:
        """Fetch calendar events from connected providers.

        Args:
            provider: Optional provider to fetch from (fetches all if None).
            hours_ahead: Hours to look ahead (defaults to settings).
            limit: Maximum events per provider (defaults to settings).

        Returns:
            List of calendar events sorted by start time.

        Raises:
            CalendarServiceError: If no providers connected or fetch fails.
        """
        effective_hours = hours_ahead or self._settings.sync_hours_ahead
        effective_limit = limit or self._settings.max_events

        events: list[CalendarEventInfo] = []

        if provider:
            provider_events = await self._fetch_provider_events(
                provider=provider,
                hours_ahead=effective_hours,
                limit=effective_limit,
            )
            events.extend(provider_events)
        else:
            # Fetch from all connected providers
            for p in [OAuthProvider.GOOGLE.value, OAuthProvider.OUTLOOK.value]:
                try:
                    provider_events = await self._fetch_provider_events(
                        provider=p,
                        hours_ahead=effective_hours,
                        limit=effective_limit,
                    )
                    events.extend(provider_events)
                except CalendarServiceError:
                    continue  # Skip disconnected providers

        # Sort by start time
        events.sort(key=lambda e: e.start_time)
        return events

    async def _fetch_provider_events(
        self,
        provider: str,
        hours_ahead: int,
        limit: int,
    ) -> list[CalendarEventInfo]:
        """Fetch events from a specific provider with token refresh."""
        oauth_provider = self._parse_provider(provider)

        async with self._uow_factory() as uow:
            integration = await uow.integrations.get_by_provider(
                provider=provider,
                integration_type=IntegrationType.CALENDAR.value,
            )

            if integration is None or not integration.is_connected:
                raise CalendarServiceError(f"Provider {provider} not connected")

            secrets = await uow.integrations.get_secrets(integration.id)
            if not secrets:
                raise CalendarServiceError(f"No tokens for provider {provider}")

            try:
                tokens = OAuthTokens.from_secrets_dict(secrets)
            except (KeyError, ValueError) as e:
                raise CalendarServiceError(f"Invalid tokens: {e}") from e

            # Refresh if expired
            if tokens.is_expired() and tokens.refresh_token:
                try:
                    tokens = await self._oauth_manager.refresh_tokens(
                        provider=oauth_provider,
                        refresh_token=tokens.refresh_token,
                    )
                    await uow.integrations.set_secrets(
                        integration_id=integration.id,
                        secrets=tokens.to_secrets_dict(),
                    )
                    await uow.commit()
                except OAuthError as e:
                    integration.mark_error(f"{ERR_TOKEN_REFRESH_PREFIX}{e}")
                    await uow.integrations.update(integration)
                    await uow.commit()
                    raise CalendarServiceError(f"{ERR_TOKEN_REFRESH_PREFIX}{e}") from e

            # Fetch events
            try:
                events = await self._fetch_events(
                    oauth_provider,
                    tokens.access_token,
                    hours_ahead,
                    limit,
                )
                integration.record_sync()
                await uow.integrations.update(integration)
                await uow.commit()
                return events
            except (GoogleCalendarError, OutlookCalendarError) as e:
                integration.mark_error(str(e))
                await uow.integrations.update(integration)
                await uow.commit()
                raise CalendarServiceError(str(e)) from e

    async def _fetch_events(
        self,
        provider: OAuthProvider,
        access_token: str,
        hours_ahead: int,
        limit: int,
    ) -> list[CalendarEventInfo]:
        """Fetch events from provider API."""
        adapter = self._get_adapter(provider)
        return await adapter.list_events(
            access_token=access_token,
            hours_ahead=hours_ahead,
            limit=limit,
        )

    async def _fetch_account_email(
        self,
        provider: OAuthProvider,
        access_token: str,
    ) -> str:
        """Fetch user email from provider API."""
        adapter = self._get_adapter(provider)
        return await adapter.get_user_email(access_token)

    def _get_adapter(
        self,
        provider: OAuthProvider,
    ) -> GoogleCalendarAdapter | OutlookCalendarAdapter:
        """Get calendar adapter for provider."""
        if provider == OAuthProvider.GOOGLE:
            return self._google_adapter
        return self._outlook_adapter

    @staticmethod
    def _parse_provider(provider: str) -> OAuthProvider:
        """Parse and validate provider string."""
        try:
            return OAuthProvider(provider.lower())
        except ValueError as e:
            raise CalendarServiceError(
                f"Invalid provider: {provider}. Must be 'google' or 'outlook'."
            ) from e

    @staticmethod
    def _map_integration_status(status: IntegrationStatus) -> str:
        """Map IntegrationStatus to connection status string."""
        return status.value if status in IntegrationStatus else IntegrationStatus.DISCONNECTED.value
