"""
Unit tests for Google Calendar OAuth endpoints.

Tests:
  - /install endpoint returns OAuth URL with HMAC state
  - /callback verifies state, stores encrypted token in DB
  - /status returns connected=True when token exists, False otherwise
  - HMAC state generation and verification work correctly
  - Missing credentials configuration handled gracefully
"""

from __future__ import annotations

import base64
import json
import uuid
from unittest.mock import AsyncMock, MagicMock, patch

import pytest


_SECRET = "test-oauth-state-secret"
_TENANT_ID = str(uuid.uuid4())


# ---------------------------------------------------------------------------
# HMAC state helper tests (reuse from channels.py)
# ---------------------------------------------------------------------------


def test_calendar_install_builds_oauth_url():
    """
    calendar_install endpoint returns a dict with a 'url' key pointing at
    accounts.google.com/o/oauth2/v2/auth.
    """
    from shared.api.calendar_auth import build_calendar_oauth_url

    url = build_calendar_oauth_url(tenant_id=_TENANT_ID, secret=_SECRET)
    assert "accounts.google.com/o/oauth2/v2/auth" in url
    assert "client_id=" in url
    assert "scope=" in url
    assert "state=" in url
    assert "access_type=offline" in url
    assert "prompt=consent" in url


def test_calendar_oauth_url_contains_signed_state():
    """State parameter in the OAuth URL encodes the tenant_id."""
    from shared.api.calendar_auth import build_calendar_oauth_url
    from shared.api.channels import verify_oauth_state

    url = build_calendar_oauth_url(tenant_id=_TENANT_ID, secret=_SECRET)

    # Extract state from URL
    import urllib.parse
    parsed = urllib.parse.urlparse(url)
    params = urllib.parse.parse_qs(parsed.query)
    state = params["state"][0]

    # Verify the state recovers the tenant_id
    recovered = verify_oauth_state(state=state, secret=_SECRET)
    assert recovered == _TENANT_ID


def test_calendar_oauth_url_uses_calendar_scope():
    """OAuth URL requests full Google Calendar scope."""
    from shared.api.calendar_auth import build_calendar_oauth_url

    url = build_calendar_oauth_url(tenant_id=_TENANT_ID, secret=_SECRET)
    assert "googleapis.com/auth/calendar" in url


# ---------------------------------------------------------------------------
# Callback token exchange and storage
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_calendar_callback_stores_encrypted_token():
    """
    handle_calendar_callback() exchanges code for tokens, encrypts them,
    and upserts a ChannelConnection with channel_type='google_calendar'.
    """
    from shared.api.calendar_auth import handle_calendar_callback

    mock_session = AsyncMock()
    mock_session.execute = AsyncMock()
    mock_session.add = MagicMock()
    mock_session.commit = AsyncMock()

    # Simulate no existing connection (first install)
    mock_result = MagicMock()
    mock_result.scalar_one_or_none.return_value = None
    mock_session.execute.return_value = mock_result

    token_response = {
        "access_token": "ya29.test_access_token",
        "refresh_token": "1//test_refresh_token",
        "token_type": "Bearer",
        "expires_in": 3600,
    }

    with (
        patch("shared.api.calendar_auth.httpx.AsyncClient") as mock_client_cls,
        patch("shared.api.calendar_auth.KeyEncryptionService") as mock_enc_cls,
        patch("shared.api.calendar_auth.settings") as mock_settings,
    ):
        mock_settings.oauth_state_secret = _SECRET
        mock_settings.google_client_id = "test-client-id"
        mock_settings.google_client_secret = "test-client-secret"
        mock_settings.portal_url = "http://localhost:3000"
        mock_settings.platform_encryption_key = "test-key"
        mock_settings.platform_encryption_key_previous = ""

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = token_response

        mock_http = AsyncMock()
        mock_http.__aenter__ = AsyncMock(return_value=mock_http)
        mock_http.__aexit__ = AsyncMock(return_value=None)
        mock_http.post = AsyncMock(return_value=mock_response)
        mock_client_cls.return_value = mock_http

        mock_enc = MagicMock()
        mock_enc.encrypt.return_value = "encrypted_token_data"
        mock_enc_cls.return_value = mock_enc

        # Generate a valid state
        from shared.api.channels import generate_oauth_state
        state = generate_oauth_state(tenant_id=_TENANT_ID, secret=_SECRET)

        redirect_url = await handle_calendar_callback(
            code="test_auth_code",
            state=state,
            session=mock_session,
        )

    # Should redirect to portal settings
    assert "settings" in redirect_url or "calendar" in redirect_url
    # Session.add should have been called (new ChannelConnection)
    mock_session.add.assert_called_once()
    # Encryption was called
    mock_enc.encrypt.assert_called_once()
    # The ChannelConnection passed to add should have google_calendar type
    conn = mock_session.add.call_args[0][0]
    assert "google_calendar" in str(conn.channel_type).lower()


@pytest.mark.asyncio
async def test_calendar_callback_invalid_state_raises():
    """handle_calendar_callback raises HTTPException for tampered state."""
    from fastapi import HTTPException
    from shared.api.calendar_auth import handle_calendar_callback

    mock_session = AsyncMock()

    with patch("shared.api.calendar_auth.settings") as mock_settings:
        mock_settings.oauth_state_secret = _SECRET

        with pytest.raises(HTTPException) as exc_info:
            await handle_calendar_callback(
                code="some_code",
                state="TAMPERED.INVALID",
                session=mock_session,
            )

    assert exc_info.value.status_code == 400


# ---------------------------------------------------------------------------
# Status endpoint
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_calendar_status_connected():
    """get_calendar_status returns connected=True when ChannelConnection exists."""
    from shared.api.calendar_auth import get_calendar_status

    mock_session = AsyncMock()
    mock_result = MagicMock()
    # Simulate existing connection
    mock_conn = MagicMock()
    mock_result.scalar_one_or_none.return_value = mock_conn
    mock_session.execute.return_value = mock_result

    tenant_id = uuid.uuid4()
    status = await get_calendar_status(tenant_id=tenant_id, session=mock_session)
    assert status["connected"] is True


@pytest.mark.asyncio
async def test_calendar_status_not_connected():
    """get_calendar_status returns connected=False when no ChannelConnection exists."""
    from shared.api.calendar_auth import get_calendar_status

    mock_session = AsyncMock()
    mock_result = MagicMock()
    mock_result.scalar_one_or_none.return_value = None
    mock_session.execute.return_value = mock_result

    tenant_id = uuid.uuid4()
    status = await get_calendar_status(tenant_id=tenant_id, session=mock_session)
    assert status["connected"] is False
