import tempfile
import pytest
import os
from config import config

# Ensure importing database at test-collection time doesn't try to open the real
# application DB. Point the app config to a temporary DB file under the
# system tempdir so the module-level MotionDatabase() in database.py can
# initialize without conflicting with a running instance.
_tmp_dir = tempfile.mkdtemp(prefix="tests_db_")
config.DATABASE_PATH = os.path.join(_tmp_dir, "motions.db")


class FakeEmbedder:
    """Real callable that returns deterministic embeddings. No network calls.

    Raises RuntimeError for any call where `fail_indices` are triggered.
    fail_indices is the set of positions (0-based) within the texts batch passed
    to a single __call__ invocation.
    """

    def __init__(self, fail_indices=None, vector_size=8):
        self.fail_indices = set(fail_indices or [])
        self.vector_size = vector_size
        self.call_count = 0
        self.calls = []  # list of (texts, kwargs) for inspection

    def __call__(self, texts, model=None, batch_size=50):
        self.call_count += 1
        self.calls.append((list(texts), {"model": model, "batch_size": batch_size}))
        results = []
        for i, text in enumerate(texts):
            if i in self.fail_indices:
                raise RuntimeError(
                    f"Simulated embedding failure for index {i}: {text!r}"
                )
            results.append([0.1 * (i + 1)] * self.vector_size)
        return results


@pytest.fixture
def mem_db(tmp_path):
    """In-memory MotionDatabase with full schema. No filesystem side effects.

    MotionDatabase(':memory:') may raise when os.path.dirname(':memory:') is
    empty. Try in-memory first, fall back to a tmp file if that fails.
    """
    from database import (
        MotionDatabase,
    )  # lazy import — database module not imported at module level

    try:
        db = MotionDatabase(":memory:")
    except Exception:
        db = MotionDatabase(str(tmp_path / "test.db"))
    yield db


@pytest.fixture
def fake_embedder():
    """FakeEmbedder with no failures by default."""
    return FakeEmbedder()


# Load test fixtures from the utils package so pytest can discover them.
pytest_plugins = ["tests.utils.migration_fixtures"]


@pytest.fixture
def tmp_duckdb_path(tmp_path):
    p = tmp_path / "test.db"
    return str(p)


@pytest.fixture
def tmp_duckdb_conn(tmp_duckdb_path):
    # Import duckdb lazily so running pytest doesn't fail on machines
    # where duckdb is not installed (CI / contributor machines that don't
    # need the duckdb-based fixtures). If duckdb is missing, skip this
    # fixture at runtime when it's requested.
    try:
        import duckdb
    except Exception:
        pytest.skip("duckdb not installed, skipping duckdb fixtures")

    conn = duckdb.connect(database=tmp_duckdb_path)
    yield conn
    try:
        conn.close()
    except Exception:
        pass


@pytest.fixture
def monkeypatch_ai_provider(monkeypatch):
    """Patch ai_provider.get_embedding to return deterministic 16-dim vector."""
    import ai_provider

    fake = [0.01] * 16
    monkeypatch.setattr(ai_provider, "get_embedding", lambda text, model=None: fake)
    return fake


@pytest.fixture
def mock_odata_client(monkeypatch):
    """
    Patch requests.Session.get for OData calls.
    Returns a configurable mock — set mock_odata_client.response to override.
    """
    import requests
    from unittest.mock import MagicMock

    mock_response = MagicMock()
    mock_response.raise_for_status.return_value = None
    mock_response.json.return_value = {"value": []}

    class MockSession:
        response = mock_response

        def get(self, *args, **kwargs):
            return self.response

    monkeypatch.setattr(requests, "Session", MockSession)
    return mock_response
