"""get_google_calendar_connection reads a tenant's Google Calendar OAuth
connection from the shared integrations table, refreshing the access token
first if it has expired. Fully isolated from a real database or network
call -- get_db_connection and httpx.post/get are mocked in every test.
"""
from datetime import datetime, timedelta, timezone
from unittest.mock import MagicMock, patch

from app.services.integrations.google_calendar import get_google_calendar_connection


def _mock_row(monkeypatch, row):
    """A fake `SELECT "accessToken", "refreshToken", "expiresAt" FROM
    {tenant}.integrations WHERE provider = 'google-calendar'` result."""
    conn = MagicMock()
    cur = conn.cursor.return_value.__enter__.return_value
    cur.fetchone.return_value = row
    monkeypatch.setattr(
        "app.services.integrations.google_calendar.get_db_connection", lambda: conn)
    return conn, cur


def test_no_row_means_not_connected(monkeypatch):
    _mock_row(monkeypatch, None)
    assert get_google_calendar_connection("org_test") is None


def test_a_disconnected_integration_is_not_used(monkeypatch):
    # The row still exists after a tenant disconnects -- status flips
    # instead of the row being deleted (same pattern platform.py's
    # set_integration_status uses for the master-DB integrations table).
    future = datetime.now(timezone.utc) + timedelta(hours=1)
    _mock_row(monkeypatch, ("access-tok", "refresh-tok", future))
    conn = MagicMock()
    cur = conn.cursor.return_value.__enter__.return_value
    cur.fetchone.return_value = None  # status filter excludes it
    monkeypatch.setattr(
        "app.services.integrations.google_calendar.get_db_connection", lambda: conn)
    assert get_google_calendar_connection("org_test") is None


def test_a_still_valid_token_is_used_without_refreshing(monkeypatch):
    future = datetime.now(timezone.utc) + timedelta(hours=1)
    _mock_row(monkeypatch, ("access-tok", "refresh-tok", future))

    calendar_response = MagicMock()
    calendar_response.raise_for_status.return_value = None
    calendar_response.json.return_value = {
        "id": "shivraj@galaxiq.ai", "timeZone": "Australia/Sydney"}
    with patch("httpx.get", return_value=calendar_response) as get, \
         patch("httpx.post") as post:
        connection = get_google_calendar_connection("org_test")

    post.assert_not_called()  # no refresh call -- token was still valid
    get.assert_called_once()
    assert connection == {
        "access_token": "access-tok",
        "calendar_email": "shivraj@galaxiq.ai",
        "timezone": "Australia/Sydney",
    }


def test_an_expired_token_is_refreshed_before_use(monkeypatch):
    past = datetime.now(timezone.utc) - timedelta(minutes=5)
    conn, cur = _mock_row(monkeypatch, ("stale-tok", "refresh-tok", past))
    monkeypatch.setattr(
        "app.services.integrations.google_calendar.settings.GOOGLE_CALENDAR_CLIENT_ID",
        "client-id")
    monkeypatch.setattr(
        "app.services.integrations.google_calendar.settings.GOOGLE_CALENDAR_CLIENT_SECRET",
        "client-secret")

    refresh_response = MagicMock()
    refresh_response.raise_for_status.return_value = None
    refresh_response.json.return_value = {"access_token": "fresh-tok", "expires_in": 3600}

    calendar_response = MagicMock()
    calendar_response.raise_for_status.return_value = None
    calendar_response.json.return_value = {
        "id": "shivraj@galaxiq.ai", "timeZone": "Australia/Sydney"}

    with patch("httpx.post", return_value=refresh_response) as post, \
         patch("httpx.get", return_value=calendar_response):
        connection = get_google_calendar_connection("org_test")

    post.assert_called_once()
    assert post.call_args.kwargs["data"]["refresh_token"] == "refresh-tok"
    assert post.call_args.kwargs["data"]["client_id"] == "client-id"
    assert connection["access_token"] == "fresh-tok"
    # The new token and expiry are written back so the next call doesn't
    # refresh again.
    update_call = next(c for c in cur.execute.call_args_list if "UPDATE" in str(c.args[0]))
    assert '"accessToken"' in str(update_call.args[0]) and '"expiresAt"' in str(update_call.args[0])
    assert update_call.args[1][0] == "fresh-tok"


def test_a_refresh_failure_is_treated_as_not_connected(monkeypatch):
    past = datetime.now(timezone.utc) - timedelta(minutes=5)
    _mock_row(monkeypatch, ("stale-tok", "revoked-refresh-tok", past))

    refresh_response = MagicMock()
    refresh_response.raise_for_status.side_effect = Exception("invalid_grant")

    with patch("httpx.post", return_value=refresh_response):
        assert get_google_calendar_connection("org_test") is None


def test_a_db_error_is_treated_as_not_connected(monkeypatch):
    conn = MagicMock()
    conn.cursor.side_effect = Exception("connection refused")
    monkeypatch.setattr(
        "app.services.integrations.google_calendar.get_db_connection", lambda: conn)
    assert get_google_calendar_connection("org_test") is None


def test_a_get_db_connection_failure_is_treated_as_not_connected(monkeypatch):
    # get_db_connection() itself can raise on real connection failure (after retries).
    # This must be caught and returned as None, not raised.
    def raise_connection_error():
        raise Exception("connection refused")
    monkeypatch.setattr(
        "app.services.integrations.google_calendar.get_db_connection",
        raise_connection_error)
    assert get_google_calendar_connection("org_test") is None
