# -*- coding: utf-8 -*-
"""
P1-2 单元测试公共配置（conftest.py）

职责：
  1. 把 skill/code/web/ 加入 sys.path，让 tests/ 下测试可直接 import safety_filter 等模块
  2. autouse fixture：把 search_engine.SEARCH_INDEX_PATHS 指向仓库内 deploy/搜索索引.json 副本，
     避免 SearchEngine() 在本地开发环境抛 FileNotFoundError（生产环境靠 /opt/troubleshoot/搜索索引.json 加载）

参考：HANDOFF.md 坑 6 —— 永远以实际代码为准；本文件不复制大索引、不污染源码。
"""

import sys
from pathlib import Path

# ============================================================
# 路径定位
# ============================================================
# conftest.py 位于 skill/code/tests/conftest.py
TESTS_DIR = Path(__file__).resolve().parent          # .../skill/code/tests
WEB_DIR = TESTS_DIR.parent / "web"                   # .../skill/code/web
REPO_ROOT = TESTS_DIR.parents[2]                     # .../troubleshoot-ai-assistant（仓库根）
INDEX_FILE = REPO_ROOT / "deploy" / "搜索索引.json"   # 仓库内现成索引副本（357 条）

# 把 web/ 加入 sys.path（置顶，优先于其它路径）
if str(WEB_DIR) not in sys.path:
    sys.path.insert(0, str(WEB_DIR))

import pytest  # noqa: E402  (sys.path 已就位后才 import)


@pytest.fixture(autouse=True)
def _patch_search_index(monkeypatch):
    """
    autouse：每个测试前，把 search_engine 的索引查找路径替换为仓库内副本。

    作用对象：search_engine.SEARCH_INDEX_PATHS（模块级常量，find_search_index() 内部遍历它）。
    对不涉及 search_engine 的测试（safety_filter / cache_manager）无副作用。
    """
    import search_engine
    monkeypatch.setattr(search_engine, "SEARCH_INDEX_PATHS", [INDEX_FILE])
    # P2-1：向量文件默认不注入（测试降级 TF-IDF），需要时由 vector_index_file fixture 注入
    monkeypatch.setattr(search_engine, "VECTOR_INDEX_PATHS", [])


@pytest.fixture
def index_file():
    """返回仓库内索引副本路径，供测试断言可用性"""
    return INDEX_FILE


# ============================================================
# 路由层测试 fixture（P1 收尾三项）
# ============================================================

# AI 调用的 mock 响应（避免路由测试真实调用 AI API）
MOCK_AI_RESPONSE = "### 🔬 排查步骤（只读操作）\nStep 1: docker ps -a 查看容器\n"


@pytest.fixture
def app(tmp_path, monkeypatch):
    """Flask app（test client 用）。

    - TESTING=True
    - mock container.get_cache_manager 返回 tmp_path 隔离的 CacheManager（不碰真实 cache 目录）
    - mock ai_service 的 call_claude_api / call_claude_api_stream（不真实调用 AI）
    - mock submit 路由的 RECORDS_DIR + rebuild_search_index（不真写文件、不真跑子进程）
    """
    # 隔离缓存目录
    import container
    from cache_manager import CacheManager
    _cache = CacheManager(tmp_path / "cache", expire_hours=24)
    monkeypatch.setattr(container, "get_cache_manager", lambda: _cache)

    # mock AI 调用
    import services.ai_service as ai_service
    monkeypatch.setattr(ai_service, "call_claude_api", lambda prompt: MOCK_AI_RESPONSE)
    monkeypatch.setattr(ai_service, "call_claude_api_stream", lambda prompt, model=None: [MOCK_AI_RESPONSE])

    # 隔离 submit 的文件写入与索引重建
    import utils.paths
    monkeypatch.setattr(utils.paths, "RECORDS_DIR", tmp_path / "records")
    monkeypatch.setattr(utils.paths, "PROJECT_ROOT", tmp_path)
    import services.record_service as record_service
    monkeypatch.setattr(record_service, "rebuild_search_index", lambda: None)

    # conftest 顶部 _patch_search_index 已 autouse 注入索引路径，此处创建 app
    import server
    application = server.create_app()
    application.config["TESTING"] = True
    return application


@pytest.fixture
def client(app):
    """未登录的 test client"""
    return app.test_client()


@pytest.fixture
def auth_client(client):
    """已登录普通用户的 test client"""
    with client.session_transaction() as sess:
        sess["user"] = {"id": 1, "username": "testuser", "role": "user"}
    return client


@pytest.fixture
def admin_client(client):
    """已登录管理员的 test client"""
    with client.session_transaction() as sess:
        sess["user"] = {"id": 2, "username": "admin", "role": "admin"}
    return client


# ============================================================
# P2-1 向量搜索 fixture
# ============================================================

import json as _json


@pytest.fixture
def vector_index_file(tmp_path):
    """生成测试用向量文件（3 条 × 8 维）"""
    vectors = {
        "model": "text-embedding-3-small",
        "dimensions": 8,
        "generated_at": "2026-07-14T10:00:00",
        "record_count": 3,
        "vectors": {
            "rec_000": [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8],
            "rec_001": [0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1],
            "rec_002": [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
        },
    }
    path = tmp_path / "搜索向量.json"
    path.write_text(_json.dumps(vectors), encoding='utf-8')
    return path


class _MockResponse:
    """requests.post 的 mock 响应"""
    def __init__(self, payload):
        self._payload = payload
        self.status_code = 200

    def raise_for_status(self):
        pass

    def json(self):
        return self._payload


@pytest.fixture
def mock_embedding_api(monkeypatch):
    """mock /v1/embeddings API 调用，返回固定 8 维向量"""
    def _fake_post(url, json=None, headers=None, timeout=None, **kwargs):
        return _MockResponse({
            "data": [{"index": 0, "embedding": [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]}]
        })
    monkeypatch.setattr("requests.post", _fake_post)
    return _fake_post


@pytest.fixture
def mock_embedding_api_failure(monkeypatch):
    """mock /v1/embeddings API 抛异常，触发降级"""
    def _fake_post(*args, **kwargs):
        raise ConnectionError("API 不可用")
    monkeypatch.setattr("requests.post", _fake_post)
    return _fake_post
