"""Shared pytest fixtures for SafeClaw test suite.

Mocks: LLM services, embedding model, retriever, test config.
No live services required — all external deps are mocked.
"""

import json
import os
import pickle
import tempfile
from pathlib import Path
from typing import List
from unittest.mock import MagicMock, patch

import pytest
import yaml

from retrieval.hybrid_search import SearchResult


# =============================================================================
# Test Config
# =============================================================================

TEST_CONFIG = {
    "app": {
        "name": "safeclaw-test",
        "env": "test",
        "mode": "offline",
        "debug": True
    },
    "models": {
        "local_llm": {
            "provider": "lmstudio",
            "base_url": "http://127.0.0.1:1234/v1",
            "model": "test-model",
            "max_tokens": 256,
            "temperature": 0.1,
            "timeout_sec": 10
        },
        "embeddings": {
            "provider": "sentence-transformers",
            "model": "all-MiniLM-L6-v2",
            "dim": 384,
            "cache_dir": None
        },
        "grok": {
            "enabled": False,
            "base_url": "https://api.x.ai/v1",
            "model": "grok-beta",
            "timeout_sec": 10,
            "max_tokens": 256,
            "temperature": 0.2
        }
    },
    "corpus": {
        "path": "data/corpus",
        "extensions": [".md", ".txt"]
    },
    "indexing": {
        "chroma_path": "",  # Set per-test via tmp_path
        "bm25_path": "",
        "collection_name": "test_kb",
        "chunk_size": 512,
        "chunk_overlap": 50,
        "batch_size": 10
    },
    "retrieval": {
        "top_k_semantic": 3,
        "top_k_keyword": 3,
        "rrf_k": 60,
        "max_context_tokens": 1000,
        "min_score": 0.75,
        "hybrid": {
            "enabled": True,
            "rrf": {"k": 60, "vector_weight": 0.6, "bm25_weight": 0.4}
        }
    },
    "policy": {
        "fallback": {
            "enabled": True,
            "require_user_confirm": True,
            "send_local_context_to_grok": False
        },
        "prompt_filter": {
            "enabled": True,
            "banned_patterns": [
                "ignore previous instructions",
                "system prompt:"
            ],
            "max_input_chars": 4000
        },
        "privacy": {
            "redact_emails": True,
            "redact_ips": True,
            "redact_secrets_like": ["AKIA[0-9A-Z]{16}"]
        }
    },
    "api": {"host": "127.0.0.1", "port": 8787, "request_timeout_sec": 30},
    "logging": {
        "level": "DEBUG",
        "log_file": "",  # Set per-test
        "audit_file": "",  # Set per-test
        "audit_fields": {
            "include_query_hash": True,
            "include_top_score": True,
            "include_retrieval_mode": True,
            "include_online_escalated": True,
            "include_model_used": True
        }
    },
    "security": {
        "require_env": ["GROK_API_KEY"],
        "allowed_origins": ["http://127.0.0.1"],
        "rate_limit": {"enabled": False, "requests_per_minute": 100}
    },
    "mcp": {
        "server_name": "safeclaw-test",
        "version": "1.0.0",
        "capabilities": {"tools": True, "sampling": False}
    }
}


@pytest.fixture
def test_config(tmp_path):
    """Write test config to temp dir, return path."""
    cfg = TEST_CONFIG.copy()
    cfg["logging"]["log_file"] = str(tmp_path / "gateway.log")
    cfg["logging"]["audit_file"] = str(tmp_path / "audit.jsonl")
    cfg["indexing"]["chroma_path"] = str(tmp_path / "chroma_db")
    cfg["indexing"]["bm25_path"] = str(tmp_path / "bm25.pkl")

    config_path = tmp_path / "config.yaml"
    with open(config_path, "w") as f:
        yaml.dump(cfg, f)

    return str(config_path), cfg


# =============================================================================
# Mock Search Results
# =============================================================================

MOCK_HIGH_SCORE_RESULTS = [
    SearchResult(
        text="Veeam immutability uses chattr +i on Linux for backup protection.",
        score=0.92,
        source="data/corpus/veeam-immutability.md",
        chunk_id=0,
        stem_tags=["veeam", "immut", "backup", "linux"],
        retrieval_mode="hybrid"
    ),
    SearchResult(
        text="Configure SOBR with S3 Object Lock for cloud immutability.",
        score=0.85,
        source="data/corpus/veeam-immutability.md",
        chunk_id=1,
        stem_tags=["sobr", "object", "lock", "cloud"],
        retrieval_mode="hybrid"
    ),
]

MOCK_LOW_SCORE_RESULTS = [
    SearchResult(
        text="Python uses virtual environments for dependency isolation.",
        score=0.30,
        source="data/corpus/python-best-practices.md",
        chunk_id=0,
        stem_tags=["python", "virtual", "depend"],
        retrieval_mode="hybrid"
    ),
]

MOCK_EMPTY_RESULTS: List[SearchResult] = []


# =============================================================================
# Mock Components
# =============================================================================

class MockRetriever:
    """Mock HybridRetriever that returns configurable results."""

    def __init__(self, results: List[SearchResult] = None):
        self.results = results if results is not None else MOCK_HIGH_SCORE_RESULTS
        self.cfg = TEST_CONFIG
        self.top_k_semantic = 3
        self.top_k_keyword = 3
        self.rrf_k = 60

    def hybrid_search(self, query: str) -> List[SearchResult]:
        return self.results

    def semantic_search(self, query: str, k=None) -> List[SearchResult]:
        return self.results

    def keyword_search(self, query: str, k=None) -> List[SearchResult]:
        return self.results


class MockLocalLLM:
    """Mock LM Studio client."""

    def __init__(self, response: str = "This is a test answer from the local LLM."):
        self.response = response
        self.last_prompt = None

    def generate(self, prompt: str, system_prompt=None) -> str:
        self.last_prompt = prompt
        return self.response


class MockGrokClient:
    """Mock Grok client."""

    def __init__(self, response: str = "This is a Grok fallback answer.", available: bool = True):
        self.response = response
        self._available = available

    def is_available(self) -> bool:
        return self._available

    def generate(self, prompt: str, system_prompt=None) -> str:
        return self.response


@pytest.fixture
def mock_retriever_high():
    return MockRetriever(MOCK_HIGH_SCORE_RESULTS)


@pytest.fixture
def mock_retriever_low():
    return MockRetriever(MOCK_LOW_SCORE_RESULTS)


@pytest.fixture
def mock_retriever_empty():
    return MockRetriever(MOCK_EMPTY_RESULTS)


@pytest.fixture
def mock_local_llm():
    return MockLocalLLM()


@pytest.fixture
def mock_grok():
    return MockGrokClient()
