"""K1.3 接线测试：`_build_provider_adapter` 为 kimi-code 走 OAuth 路径 + 按需刷新。

不联网：refresh 路径 monkeypatch httpx + kimi_oauth.refresh。断言：
- 凭据未临近过期 → 直接用现有 access token 建 KimiCodeAdapter（不刷新）；
- 临近过期 → 调 refresh + 持久化新包 + 用新 access token 建适配器；
- 无 oauth 凭据 → LLM_UNAVAILABLE。
"""

from __future__ import annotations

import os
from datetime import UTC, datetime, timedelta
from typing import Any

import pytest
from fakes_providers import FakeCredentialStore
from ww_api.services.credentials import STUB_OWNER_ID
from ww_api.services.kimi_oauth import TokenSet, decrypt_oauth_bundle, encrypt_oauth_bundle
from ww_llm_gateway.adapters.kimi_code import KIMI_CODE_PROVIDER, KimiCodeAdapter
from ww_shared import AppError, ErrorCode

_ENC_KEY = "imB6TEcV2AzlpXjR4NjTgK_9Zt-OoqaLFQc3XqBQ0CQ="


def _store_with_token(token: TokenSet) -> FakeCredentialStore:
    store = FakeCredentialStore()
    store.oauth[(STUB_OWNER_ID, KIMI_CODE_PROVIDER)] = encrypt_oauth_bundle(token, key=_ENC_KEY)
    return store


def _set_env() -> None:
    os.environ["CREDENTIAL_ENC_KEY"] = _ENC_KEY
    from ww_config import get_settings

    get_settings.cache_clear()


@pytest.mark.asyncio
async def test_build_adapter_uses_existing_token_when_fresh() -> None:
    _set_env()
    from ww_api.services.project_deps import _build_provider_adapter

    fresh = TokenSet(
        access_token="fresh-acc",
        refresh_token="ref",
        expires_at=datetime.now(UTC) + timedelta(seconds=900),
    )
    store = _store_with_token(fresh)

    adapter = await _build_provider_adapter(store, KIMI_CODE_PROVIDER)

    assert isinstance(adapter, KimiCodeAdapter)
    # 未刷新：oauth 凭据未变。
    cred = store.oauth[(STUB_OWNER_ID, KIMI_CODE_PROVIDER)]
    assert decrypt_oauth_bundle(cred, key=_ENC_KEY).access_token == "fresh-acc"


@pytest.mark.asyncio
async def test_build_adapter_refreshes_when_near_expiry(monkeypatch: pytest.MonkeyPatch) -> None:
    _set_env()
    from ww_api.services import project_deps
    from ww_api.services.project_deps import _build_provider_adapter

    near = TokenSet(
        access_token="old-acc",
        refresh_token="old-ref",
        expires_at=datetime.now(UTC) + timedelta(seconds=30),  # 临近过期
    )
    store = _store_with_token(near)

    refreshed = TokenSet(
        access_token="new-acc",
        refresh_token="new-ref",
        expires_at=datetime.now(UTC) + timedelta(seconds=900),
    )

    async def _fake_refresh(_http: Any, _refresh_token: str) -> TokenSet:
        assert _refresh_token == "old-ref"
        return refreshed

    # 替换 refresh + httpx.AsyncClient（避免真联网）。
    monkeypatch.setattr(project_deps, "kimi_refresh", _fake_refresh)

    class _FakeAsyncClient:
        async def __aenter__(self) -> _FakeAsyncClient:
            return self

        async def __aexit__(self, *exc: object) -> None:
            return None

    monkeypatch.setattr(
        "ww_api.services.project_deps.httpx.AsyncClient", lambda *a, **k: _FakeAsyncClient()
    )

    adapter = await _build_provider_adapter(store, KIMI_CODE_PROVIDER)

    assert isinstance(adapter, KimiCodeAdapter)
    # 刷新后新包持久化（下次复用刷新结果）。
    cred = store.oauth[(STUB_OWNER_ID, KIMI_CODE_PROVIDER)]
    assert decrypt_oauth_bundle(cred, key=_ENC_KEY).access_token == "new-acc"


@pytest.mark.asyncio
async def test_build_adapter_no_credential_returns_none() -> None:
    _set_env()
    from ww_api.services.project_deps import _build_provider_adapter

    store = FakeCredentialStore()  # 无任何凭据
    adapter = await _build_provider_adapter(store, KIMI_CODE_PROVIDER)
    assert adapter is None


@pytest.mark.asyncio
async def test_build_adapter_raises_when_oauth_missing_blob() -> None:
    """auth_type=oauth 但 oauth_enc=None（数据不一致）→ LLM_UNAVAILABLE。"""
    _set_env()
    from ww_api.services.credentials import StoredCredential
    from ww_api.services.project_deps import _resolve_kimi_code_token

    bad = StoredCredential(
        provider=KIMI_CODE_PROVIDER, api_key_enc=None, auth_type="oauth", oauth_enc=None
    )
    with pytest.raises(AppError) as exc:
        await _resolve_kimi_code_token(FakeCredentialStore(), bad, _ENC_KEY)
    assert exc.value.code == ErrorCode.LLM_UNAVAILABLE
