"""
Google Calendar OAuth API endpoints — per-tenant OAuth install + callback.

Endpoints:
  GET  /api/portal/calendar/install?tenant_id={id}
         → generates HMAC-signed state, returns Google OAuth URL
  GET  /api/portal/calendar/callback?code={code}&state={state}
         → verifies state, exchanges code for tokens, stores encrypted in channel_connections
  GET  /api/portal/calendar/{tenant_id}/status
         → returns {"connected": bool}

OAuth state uses the same HMAC-SHA256 signed state pattern as Slack OAuth
(see shared.api.channels.generate_oauth_state / verify_oauth_state).

Token storage:
  Token JSON is encrypted with the platform KeyEncryptionService (Fernet) and
  stored in channel_connections with channel_type='google_calendar'.
  workspace_id is set to str(tenant_id) — Google Calendar is per-tenant,
  not per-workspace, so the tenant UUID serves as the workspace identifier.

Token auto-refresh:
  The calendar_lookup tool handles refresh via google-auth library.
  This module is responsible for initial OAuth install and status checks only.
"""

from __future__ import annotations

import json
import uuid
from typing import Any

import httpx
from fastapi import APIRouter, Depends, HTTPException, Query, status
from fastapi.responses import RedirectResponse
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from shared.api.channels import generate_oauth_state, verify_oauth_state
from shared.api.rbac import PortalCaller, require_tenant_admin, require_tenant_member
from shared.config import settings
from shared.crypto import KeyEncryptionService
from shared.db import get_session
from shared.models.tenant import ChannelConnection, ChannelTypeEnum

calendar_auth_router = APIRouter(prefix="/api/portal/calendar", tags=["calendar"])

# Google Calendar OAuth scopes — full read+write (locked decision: operators need CRUD)
_CALENDAR_SCOPE = "https://www.googleapis.com/auth/calendar"

# Google OAuth endpoints
_GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"
_GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token"


# ---------------------------------------------------------------------------
# Helper: build OAuth URL
# ---------------------------------------------------------------------------


def build_calendar_oauth_url(tenant_id: str, secret: str) -> str:
    """
    Build a Google OAuth 2.0 authorization URL for Calendar access.

    Args:
        tenant_id: Tenant UUID as string — embedded in the HMAC-signed state.
        secret:    HMAC secret for state generation (oauth_state_secret).

    Returns:
        Full Google OAuth authorization URL ready to redirect the user to.
    """
    state = generate_oauth_state(tenant_id=tenant_id, secret=secret)
    redirect_uri = f"{settings.portal_url}/api/portal/calendar/callback"

    params = (
        f"?client_id={settings.google_client_id}"
        f"&redirect_uri={redirect_uri}"
        f"&response_type=code"
        f"&scope={_CALENDAR_SCOPE}"
        f"&access_type=offline"
        f"&prompt=consent"
        f"&state={state}"
    )
    return f"{_GOOGLE_AUTH_URL}{params}"


def _get_encryption_service() -> KeyEncryptionService:
    """Return the platform-level KeyEncryptionService."""
    if not settings.platform_encryption_key:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="PLATFORM_ENCRYPTION_KEY not configured",
        )
    return KeyEncryptionService(
        primary_key=settings.platform_encryption_key,
        previous_key=settings.platform_encryption_key_previous,
    )


# ---------------------------------------------------------------------------
# Endpoint: GET /install
# ---------------------------------------------------------------------------


@calendar_auth_router.get("/install")
async def calendar_install(
    tenant_id: uuid.UUID = Query(...),
    caller: PortalCaller = Depends(require_tenant_admin),
) -> dict[str, str]:
    """
    Generate the Google Calendar OAuth authorization URL.

    Returns a JSON object with a 'url' key. The operator's browser should
    be redirected to this URL to begin the Google OAuth consent flow.
    """
    if not settings.oauth_state_secret:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="OAUTH_STATE_SECRET not configured",
        )
    if not settings.google_client_id:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="GOOGLE_CLIENT_ID not configured",
        )

    url = build_calendar_oauth_url(
        tenant_id=str(tenant_id),
        secret=settings.oauth_state_secret,
    )
    return {"url": url}


# ---------------------------------------------------------------------------
# Callback handler (shared between endpoint and tests)
# ---------------------------------------------------------------------------


async def handle_calendar_callback(
    code: str,
    state: str,
    session: AsyncSession,
) -> str:
    """
    Process the Google OAuth callback: verify state, exchange code, store token.

    Args:
        code:    Authorization code from Google.
        state:   HMAC-signed state parameter.
        session: Async DB session for storing the ChannelConnection.

    Returns:
        Redirect URL string (portal /settings?calendar=connected).

    Raises:
        HTTPException 400 if state is invalid or token exchange fails.
    """
    if not settings.oauth_state_secret:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="OAUTH_STATE_SECRET not configured",
        )

    # Verify HMAC state to recover tenant_id
    try:
        tenant_id_str = verify_oauth_state(state=state, secret=settings.oauth_state_secret)
        tenant_id = uuid.UUID(tenant_id_str)
    except (ValueError, Exception) as exc:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Invalid OAuth state: {exc}",
        ) from exc

    redirect_uri = f"{settings.portal_url}/api/portal/calendar/callback"

    # Exchange authorization code for tokens
    async with httpx.AsyncClient(timeout=30.0) as client:
        response = await client.post(
            _GOOGLE_TOKEN_URL,
            data={
                "code": code,
                "client_id": settings.google_client_id,
                "client_secret": settings.google_client_secret,
                "redirect_uri": redirect_uri,
                "grant_type": "authorization_code",
            },
        )

    if response.status_code != 200:
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail="Google token exchange failed",
        )

    token_data: dict[str, Any] = response.json()
    if "error" in token_data:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Google OAuth error: {token_data.get('error_description', token_data['error'])}",
        )

    # Build token JSON for storage (google-auth credentials format)
    token_json = json.dumps({
        "token": token_data.get("access_token", ""),
        "refresh_token": token_data.get("refresh_token", ""),
        "token_uri": _GOOGLE_TOKEN_URL,
        "client_id": settings.google_client_id,
        "client_secret": settings.google_client_secret,
        "scopes": [_CALENDAR_SCOPE],
    })

    # Encrypt before storage
    enc_svc = _get_encryption_service()
    encrypted_token = enc_svc.encrypt(token_json)

    # Upsert ChannelConnection for google_calendar
    existing = await session.execute(
        select(ChannelConnection).where(
            ChannelConnection.tenant_id == tenant_id,
            ChannelConnection.channel_type == ChannelTypeEnum.GOOGLE_CALENDAR,
        )
    )
    conn = existing.scalar_one_or_none()

    if conn is None:
        conn = ChannelConnection(
            tenant_id=tenant_id,
            channel_type=ChannelTypeEnum.GOOGLE_CALENDAR,
            workspace_id=str(tenant_id),  # tenant UUID as workspace_id
            config={"token": encrypted_token},
        )
        session.add(conn)
    else:
        conn.config = {"token": encrypted_token}

    await session.commit()

    return f"{settings.portal_url}/settings?calendar=connected"


# ---------------------------------------------------------------------------
# Endpoint: GET /callback
# ---------------------------------------------------------------------------


@calendar_auth_router.get("/callback")
async def calendar_callback(
    code: str = Query(...),
    state: str = Query(...),
    session: AsyncSession = Depends(get_session),
) -> RedirectResponse:
    """
    Handle the Google Calendar OAuth callback from Google.

    No auth guard — this endpoint receives an external redirect from Google
    (no session cookie available during OAuth flow).

    Verifies HMAC state, exchanges code for tokens, stores encrypted token,
    then redirects to portal /settings?calendar=connected.
    """
    redirect_url = await handle_calendar_callback(code=code, state=state, session=session)
    return RedirectResponse(url=redirect_url, status_code=status.HTTP_302_FOUND)


# ---------------------------------------------------------------------------
# Status check helper (for tests)
# ---------------------------------------------------------------------------


async def get_calendar_status(
    tenant_id: uuid.UUID,
    session: AsyncSession,
) -> dict[str, bool]:
    """
    Check if a Google Calendar connection exists for a tenant.

    Args:
        tenant_id: Tenant UUID to check.
        session:   Async DB session.

    Returns:
        {"connected": True} if a ChannelConnection exists, {"connected": False} otherwise.
    """
    result = await session.execute(
        select(ChannelConnection).where(
            ChannelConnection.tenant_id == tenant_id,
            ChannelConnection.channel_type == ChannelTypeEnum.GOOGLE_CALENDAR,
        )
    )
    conn = result.scalar_one_or_none()
    return {"connected": conn is not None}


# ---------------------------------------------------------------------------
# Endpoint: GET /{tenant_id}/status
# ---------------------------------------------------------------------------


@calendar_auth_router.get("/{tenant_id}/status")
async def calendar_status(
    tenant_id: uuid.UUID,
    caller: PortalCaller = Depends(require_tenant_member),
    session: AsyncSession = Depends(get_session),
) -> dict[str, bool]:
    """
    Check if Google Calendar is connected for a tenant.

    Returns {"connected": true} if the tenant has authorized Google Calendar,
    {"connected": false} otherwise.
    """
    return await get_calendar_status(tenant_id=tenant_id, session=session)
