"""Vault integration tests."""

from __future__ import annotations

import shutil
from dataclasses import replace
from pathlib import Path

import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from app.api.routes import assistant as assistant_routes
from app.api.routes import configuration as config_routes
from app.api.routes import vault as vault_routes
from app.core.db import get_db
from app.models.base import Base
from app.models.vault import VaultChunk, VaultFile, VaultPath  # noqa: F401
from app.services.ai import provider_registry as ai_provider_registry
from app.services import assistant_service
from app.services import runtime_config
from app.services import vault as vault_service


@pytest.fixture()
def client(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> TestClient:
    """Create isolated FastAPI client with temporary SQLite DB."""
    db_path = tmp_path / "vault_test.db"
    engine = create_engine(
        f"sqlite:///{db_path}",
        connect_args={"check_same_thread": False},
    )
    SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

    Base.metadata.create_all(bind=engine)
    vault_service.ensure_vault_fts(engine)

    config_path = tmp_path / ".sentineledge" / "config.json"
    monkeypatch.setenv("SENTINELEDGE_CONFIG_PATH", str(config_path))
    runtime_config.reset_runtime_state()

    app = FastAPI()
    app.include_router(config_routes.router)
    app.include_router(vault_routes.router)
    app.include_router(assistant_routes.router)

    def override_get_db():
        db = SessionLocal()
        try:
            yield db
        finally:
            db.close()

    app.dependency_overrides[get_db] = override_get_db
    monkeypatch.setattr(
        assistant_service,
        "settings",
        replace(assistant_service.settings, gemini_api_key=None),
    )
    patched_settings = replace(
        runtime_config.settings,
        ai_provider="gemini",
        gemini_api_key=None,
        ai_allow_fallback=False,
        ollama_model="llama3.2:3b",
    )
    monkeypatch.setattr(runtime_config, "settings", patched_settings)
    monkeypatch.setattr(ai_provider_registry, "settings", replace(patched_settings))

    with TestClient(app) as test_client:
        yield test_client

    app.dependency_overrides.clear()
    runtime_config.reset_runtime_state()
    engine.dispose()


def _prepare_vault_dir(tmp_path: Path) -> Path:
    """Copy static vault fixture into temporary path."""
    source = Path(__file__).resolve().parent / "data" / "vault"
    target = tmp_path / "vault"
    shutil.copytree(source, target)
    return target


def test_register_vault_path(client: TestClient, tmp_path: Path) -> None:
    """Registering a valid path stores canonical location."""
    vault_dir = _prepare_vault_dir(tmp_path)

    response = client.post("/vault/register", json={"path": str(vault_dir)})
    assert response.status_code == 201

    payload = response.json()
    assert payload["path"] == str(vault_dir.resolve())
    assert payload["enabled"] is True


def test_index_and_search_returns_expected_file(client: TestClient, tmp_path: Path) -> None:
    """Indexing vault then searching should return kerberos evidence."""
    vault_dir = _prepare_vault_dir(tmp_path)

    register_response = client.post("/vault/register", json={"path": str(vault_dir)})
    assert register_response.status_code == 201

    index_response = client.post("/vault/index", json={})
    assert index_response.status_code == 200
    stats = index_response.json()["stats"]
    assert stats["files_scanned"] >= 2
    assert stats["files_indexed"] >= 1

    status_response = client.get("/vault/status")
    assert status_response.status_code == 200
    status_payload = status_response.json()
    assert status_payload["files"] >= 2
    assert status_payload["chunks"] >= 2
    assert status_payload["enabled_files"] >= 2
    assert status_payload["enabled_chunks"] >= 2
    assert status_payload["needs_index"] is False

    search_response = client.get("/vault/search", params={"q": "kerberos", "limit": 5})
    assert search_response.status_code == 200
    results = search_response.json()["results"]
    assert results
    assert any("auth_notes.md" in item["file_path"] for item in results)


def test_assistant_vault_query_includes_sources(client: TestClient, tmp_path: Path) -> None:
    """Vault-aware assistant response should include citations."""
    vault_dir = _prepare_vault_dir(tmp_path)
    assert client.post("/config/save", json={"vault_paths": [str(vault_dir)]}).status_code == 200
    assert client.post("/vault/register", json={"path": str(vault_dir)}).status_code == 201
    assert client.post("/vault/index", json={}).status_code == 200

    assistant_response = client.post(
        "/assistant/query",
        json={"message": "según mis notas, explícame kerberos en simple"},
    )
    assert assistant_response.status_code == 200
    payload = assistant_response.json()

    assert payload["intent"] == "VAULT"
    assert "Sources:" in payload["response"]
    assert "auth_notes.md" in payload["response"] or "network.txt" in payload["response"]


def test_index_is_recursive_and_ignores_known_junk_dirs(client: TestClient, tmp_path: Path) -> None:
    """Recursive scan should include nested folders and skip junk dirs like .git."""
    vault_dir = tmp_path / "vault_recursive"
    nested = vault_dir / "nested" / "deep"
    hidden_custom = vault_dir / ".custom" / "inner"
    git_dir = vault_dir / ".git"
    nested.mkdir(parents=True, exist_ok=True)
    hidden_custom.mkdir(parents=True, exist_ok=True)
    git_dir.mkdir(parents=True, exist_ok=True)

    (nested / "ia.md").write_text("La inteligencia artificial ayuda con procesadores modernos.", encoding="utf-8")
    (hidden_custom / "private.md").write_text("Procesadores en carpeta oculta no-junk.", encoding="utf-8")
    (git_dir / "ignored.md").write_text("gitjunktoken", encoding="utf-8")

    assert client.post("/vault/register", json={"path": str(vault_dir)}).status_code == 201
    index_response = client.post("/vault/index", json={})
    assert index_response.status_code == 200
    stats = index_response.json()["stats"]
    assert stats["files_scanned"] >= 2

    search_response = client.get("/vault/search", params={"q": "procesadores", "limit": 20})
    assert search_response.status_code == 200
    results = search_response.json()["results"]
    assert any("nested/deep/ia.md" in item["file_path"] for item in results)
    assert any(".custom/inner/private.md" in item["file_path"] for item in results)
    assert all("/.git/" not in item["file_path"] for item in results)


def test_vault_search_reports_not_indexed_when_enabled_paths_have_no_chunks(client: TestClient, tmp_path: Path) -> None:
    """Search should report not-indexed state instead of empty-noise responses."""
    vault_dir = _prepare_vault_dir(tmp_path)
    assert client.post("/config/save", json={"vault_paths": [str(vault_dir)]}).status_code == 200
    assert client.post("/vault/register", json={"path": str(vault_dir)}).status_code == 201

    response = client.get("/vault/search", params={"q": "kerberos", "limit": 5})
    assert response.status_code == 409
    assert "not indexed" in str(response.json().get("detail", "")).lower()


def test_assistant_auto_indexes_after_enabled_path_switch(client: TestClient, tmp_path: Path) -> None:
    """Assistant retrieval should auto-index when enabled path was switched and has no chunks yet."""
    parent = tmp_path / "vault_parent"
    child = parent / "child"
    child.mkdir(parents=True, exist_ok=True)
    (child / "cpu_ai.md").write_text(
        "Los procesadores modernos usan tecnicas de inteligencia artificial para optimizar rendimiento.",
        encoding="utf-8",
    )

    assert client.post("/config/save", json={"vault_paths": [str(parent)]}).status_code == 200
    assert client.post("/vault/register", json={"path": str(parent)}).status_code == 201
    assert client.post("/vault/index", json={}).status_code == 200

    switch_response = client.post("/config/save", json={"vault_paths": [str(child)]})
    assert switch_response.status_code == 200

    status_before = client.get("/vault/status")
    assert status_before.status_code == 200
    status_before_payload = status_before.json()
    assert status_before_payload["enabled_paths"] == 1
    assert status_before_payload["enabled_chunks"] == 0
    assert status_before_payload["needs_index"] is True

    assistant_response = client.post(
        "/assistant/query",
        json={"message": "según mis notas del vault, resume procesadores e inteligencia artificial"},
    )
    assert assistant_response.status_code == 200
    payload = assistant_response.json()
    assert payload["intent"] == "VAULT"
    assert "Sources:" in payload["response"]
    assert "Sources:\n- none" not in payload["response"]
    assert "vault_not_indexed" not in payload["response"]

    status_after = client.get("/vault/status")
    assert status_after.status_code == 200
    status_after_payload = status_after.json()
    assert status_after_payload["enabled_chunks"] > 0
    assert status_after_payload["needs_index"] is False
