"""
连接方案可切换驱动层 · 离线回归（不依赖真机/8899）

验证：
  - 默认 4 类方案存在（jiqing 默认 + aochuang/legacy 内置 + 自定义可注册）
  - 三级开关（global/project/device）解析优先级
  - 禁用方案回退 jiqing
  - 自定义方案注册 / 删除 / 内置不可删
  - http 驱动字段映射（body_map / query_map）

注意：本测试为离线 mock 回归，按真机铁律仅作回归，不替代真机 E2E。
"""

import os
import sys
import tempfile
import importlib

import pytest

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "app"))


@pytest.fixture()
def mgr(tmp_path, monkeypatch):
    import services.connection_provider as cp
    importlib.reload(cp)
    # 重定向持久化到临时目录
    monkeypatch.setattr(cp, "_DATA_DIR", tmp_path)
    monkeypatch.setattr(cp, "_CONFIG_PATH", tmp_path / "connection_providers.json")
    m = cp.ConnectionProviderManager()
    m.load(force=True)
    return m, cp


def test_builtin_providers(mgr):
    m, cp = mgr
    ids = {p["id"] for p in m.list_providers()}
    assert {"jiqing", "aochuang", "legacy"} <= ids
    # 默认 active = jiqing
    assert m.resolve_active_id() == "jiqing"
    jq = m.get("jiqing")
    assert jq.kind == "native" and jq.enabled and jq.builtin


def test_switch_requires_enabled(mgr):
    m, cp = mgr
    # aochuang 默认未启用 → 切换应拒绝
    with pytest.raises(ValueError):
        m.switch("aochuang", scope=cp.SCOPE_GLOBAL)
    # 启用后可切
    m.register({"id": "aochuang", "enabled": True, "base_url": "https://007.example/api"})
    res = m.switch("aochuang", scope=cp.SCOPE_GLOBAL)
    assert res["switched_to"] == "aochuang"
    assert m.resolve_active_id() == "aochuang"


def test_scope_priority(mgr):
    m, cp = mgr
    m.register({"id": "aochuang", "enabled": True, "base_url": "https://007.example/api"})
    m.register({"id": "legacy", "enabled": True, "base_url": "https://legacy.example"})
    m.switch("aochuang", scope=cp.SCOPE_GLOBAL)
    m.switch("legacy", scope=cp.SCOPE_PROJECT, project_id="cunkebao")
    m.switch("jiqing", scope=cp.SCOPE_DEVICE, device_id="dev1")
    # 设备级最高
    assert m.resolve_active_id(device_id="dev1", project_id="cunkebao") == "jiqing"
    # 项目级次之
    assert m.resolve_active_id(project_id="cunkebao") == "legacy"
    # 全局兜底
    assert m.resolve_active_id() == "aochuang"


def test_disabled_active_falls_back(mgr):
    m, cp = mgr
    m.register({"id": "aochuang", "enabled": True, "base_url": "https://007.example/api"})
    m.switch("aochuang", scope=cp.SCOPE_GLOBAL)
    assert m.resolve_active_id() == "aochuang"
    # 再禁用 aochuang → 回退 jiqing
    m.register({"id": "aochuang", "enabled": False})
    assert m.resolve_active_id() == "jiqing"


def test_custom_register_and_remove(mgr):
    m, cp = mgr
    meta = m.register({
        "id": "custom_x", "name": "我的自建方案", "kind": "http", "enabled": True,
        "base_url": "https://x.example", "endpoints": {"send_message": {"method": "POST", "path": "/s"}},
    })
    assert meta["id"] == "custom_x" and meta["builtin"] is False
    assert "send_message" in meta["capabilities"]
    m.remove("custom_x")
    assert m.get("custom_x") is None
    # 内置不可删
    with pytest.raises(ValueError):
        m.remove("jiqing")


def test_http_field_mapping(mgr):
    m, cp = mgr
    prov = cp.HttpConnectionProvider({
        "id": "t", "kind": "http", "base_url": "https://x",
        "endpoints": {
            "send_message": {"method": "POST", "path": "/send",
                             "body_map": {"device_id": "deviceId", "to_id": "wxid", "content": "content"}},
        },
    })
    src = {"device_id": "d1", "platform": "wechat", "to_id": "u1", "content": "hi", "msg_type": "text"}
    body = prov._apply_map(src, {"device_id": "deviceId", "to_id": "wxid", "content": "content"})
    assert body["deviceId"] == "d1" and body["wxid"] == "u1" and body["content"] == "hi"
    # 未映射字段透传保留
    assert body.get("platform") == "wechat"


def test_persistence_roundtrip(mgr):
    m, cp = mgr
    m.register({"id": "aochuang", "enabled": True, "base_url": "https://007.example/api"})
    m.switch("aochuang", scope=cp.SCOPE_GLOBAL)
    # 新实例从磁盘加载
    m2 = cp.ConnectionProviderManager()
    m2.load(force=True)
    assert m2.resolve_active_id() == "aochuang"


def test_http_execute_unknown_action_501(mgr):
    import asyncio
    m, cp = mgr
    prov = cp.HttpConnectionProvider({"id": "t", "kind": "http", "base_url": "https://x", "endpoints": {}})
    res = asyncio.run(prov.execute("d1", "wechat", "send_message", {"to_id": "u", "content": "c"}))
    assert res["code"] == 501 and res["success"] is False


def test_jiqing_execute_offline_payload(mgr):
    """jiqing 原生方案在无 WS 设备时应返回离线 503（复用 device_transport，不 mock 成功）。"""
    import asyncio
    m, cp = mgr
    prov = m.get("jiqing")
    res = asyncio.run(prov.execute("nonexist_dev", "wechat", "send_message",
                                   {"to_id": "filehelper", "content": "hi"}))
    assert res.get("provider") == "jiqing"
    assert res.get("success") is False  # 离线设备，不得假成功
