# backend/tests/test_phase16_calendar.py — phase-16 Outlook/M365 캘린더 OAuth + Graph sync
import time

import pytest
from sqlmodel import select

from app.config import get_settings
from app.connectors import oauth
from app.connectors.calendar import real_outlook
from app.connectors.calendar.real_outlook import OutlookCalendarConnector
from app.connectors.registry import ConnectorRegistry, _impl
from app.crypto import encrypt_token
from app.models import CalEvent, ConnectorAccount, ConnectorDomain, ConnectorMode, ConnState


@pytest.fixture()
def ms_creds(monkeypatch):
    monkeypatch.setenv("MICROSOFT_CLIENT_ID", "m-cid")
    monkeypatch.setenv("MICROSOFT_CLIENT_SECRET", "m-sec")
    get_settings.cache_clear()
    yield
    get_settings.cache_clear()


class _Resp:
    def __init__(self, payload, status=200):
        self._p = payload
        self.status_code = status
        self.headers = {}

    def json(self):
        return self._p

    def raise_for_status(self):
        if self.status_code >= 400:
            raise RuntimeError(f"http {self.status_code}")


class _Client:
    """httpx.Client 컨텍스트매니저 스텁 — 페이지를 순서대로 반환."""

    def __init__(self, pages):
        self._pages = list(pages)
        self.calls = []

    def __enter__(self):
        return self

    def __exit__(self, *a):
        return False

    def get(self, url, headers=None, params=None):
        self.calls.append((url, params))
        return _Resp(self._pages.pop(0))


# ── OAuth: Outlook 캘린더 authorize URL (microsoft 테넌트 + Calendars.Read) ──
def test_outlook_calendar_authorize_url(session, ms_creds):
    s, _ = session
    url = oauth.start_oauth(s, "calendar", "outlook_calendar", redirect_after="/settings")
    assert url.startswith("https://login.microsoftonline.com/common/oauth2/v2.0/authorize")
    assert "code_challenge=" in url and "state=" in url  # PKCE
    assert "Calendars.Read" in url and "offline_access" in url
    assert "Mail.Read" not in url  # 캘린더 스코프엔 메일 권한 없음


# ── registry: provider 로 Google/Outlook 캘린더 분기 ──
def test_calendar_dispatch_by_provider(session):
    s, _ = session
    assert _impl("calendar", "real", "outlook_calendar").__name__ == "OutlookCalendarConnector"
    assert _impl("calendar", "real", "google_calendar").__name__ == "GoogleCalendarConnector"
    out_acct = ConnectorAccount(
        id="ca-cal-o",
        domain=ConnectorDomain.calendar,
        provider="outlook_calendar",
        mode=ConnectorMode.real,
    )
    assert isinstance(ConnectorRegistry.get(s, out_acct), OutlookCalendarConnector)


# ── fetch: calendarView 페이지네이션(nextLink) + isCancelled 스킵 + CalEvent upsert ──
def test_outlook_calendar_fetch_paginates_and_upserts(session, monkeypatch):
    s, _ = session
    acct = ConnectorAccount(
        id="ca-cal-outlook-1",
        domain=ConnectorDomain.calendar,
        provider="outlook_calendar",
        mode=ConnectorMode.real,
        state=ConnState.connected,
        external_account_id="me@corp.com",
        token_enc=encrypt_token({"access_token": "AT", "expires_at": int(time.time()) + 9999}),
    )
    s.add(acct)
    s.commit()

    ev = lambda i, **k: {  # noqa: E731
        "id": f"ev-{i}",
        "subject": f"미팅 {i}",
        "start": {"dateTime": "2026-06-12T09:00:00.0000000"},
        "end": {"dateTime": "2026-06-12T09:30:00.0000000"},
        "location": {"displayName": "룸"},
        **k,
    }
    pages = [
        {"value": [ev(1), ev(2, isCancelled=True)], "@odata.nextLink": "https://graph/next?p=2"},
        {"value": [ev(3)]},
    ]
    client = _Client(pages)
    monkeypatch.setattr(real_outlook.httpx, "Client", lambda *a, **k: client)

    conn = OutlookCalendarConnector(acct)
    res = conn.sync(s)

    # 취소 이벤트 제외하고 2건만 적재(ev-1, ev-3)
    assert res.upserted == 2
    titles = {e.title for e in s.exec(select(CalEvent)).all()}
    assert titles == {"미팅 1", "미팅 3"}
    # 2번째 호출은 nextLink URL + params=None (쿼리는 nextLink 에 포함)
    assert client.calls[0][0].endswith("/me/calendarView")
    assert client.calls[1] == ("https://graph/next?p=2", None)


# ── API: /connectors/providers?domain=calendar → google + outlook 캘린더 ──
def test_calendar_providers_endpoint(client, ms_creds, monkeypatch):
    monkeypatch.setenv("GOOGLE_CLIENT_ID", "g-cid")
    get_settings.cache_clear()
    rows = client.get("/api/connectors/providers?domain=calendar").json()
    by = {r["provider"]: r for r in rows}
    assert set(by) == {"google_calendar", "outlook_calendar"}
    assert by["outlook_calendar"]["configured"] is True  # ms_creds 설정됨
    assert by["outlook_calendar"]["label"] == "Outlook 캘린더"


# ── 시작 검증: 미구성 outlook_calendar 는 400 ──
def test_oauth_start_outlook_calendar_requires_microsoft_client_id(client):
    r = client.get("/api/connectors/oauth/start?domain=calendar&provider=outlook_calendar")
    assert r.status_code == 400
