"""Shared pytest fixtures for the zm-rag test suite.

Two test profiles:
  1. **Unit / ASGI** (default) — uses ``httpx.AsyncClient`` bound to the ASGI app
     via ``asgi-lifespan`` so that startup/shutdown hooks (ES/Neo4j/Redis init)
     are executed just like a real server.

  2. **Integration** — uses a real HTTP base_url pointing at a running backend
     (``--base-url http://localhost:8900``). Requires Docker infra + uvicorn.

Fixtures auto-select the right mode based on the ``--base-url`` CLI flag.
"""

from __future__ import annotations

import json
import os
import sys
from pathlib import Path
from typing import Any, AsyncIterator

import pytest
import pytest_asyncio
from httpx import ASGITransport, AsyncClient

# Ensure backend package is importable
_BACKEND_DIR = Path(__file__).resolve().parent.parent
if str(_BACKEND_DIR) not in sys.path:
    sys.path.insert(0, str(_BACKEND_DIR))

from app.config import settings


# ---------------------------------------------------------------------------
# CLI option
# ---------------------------------------------------------------------------

def pytest_addoption(parser):
    parser.addoption(
        "--base-url",
        default="",
        help="If set, tests run against a live server instead of ASGI transport.",
    )


# ---------------------------------------------------------------------------
# Session-scoped basics
# ---------------------------------------------------------------------------

@pytest.fixture(scope="session")
def anyio_backend():
    return "asyncio"


@pytest.fixture(scope="session")
def base_url(request) -> str:
    return request.config.getoption("--base-url") or ""


# ---------------------------------------------------------------------------
# HTTP client — auto-selects ASGI or live mode
# ---------------------------------------------------------------------------

@pytest_asyncio.fixture()
async def client(base_url) -> AsyncIterator[AsyncClient]:
    """Yield an ``AsyncClient``.

    * ASGI mode: uses ``asgi-lifespan`` LifespanManager so that the FastAPI
      lifespan hooks (ES/Neo4j/Redis init) run correctly.
    * Live mode: plain HTTP client pointed at ``--base-url``

    The original FastAPI ``app`` instance is attached as ``client.app``
    so that tests can access ``client.app.state.es_client`` etc.
    """
    if base_url:
        async with AsyncClient(base_url=base_url, timeout=60) as ac:
            ac.app = None  # type: ignore[attr-defined]
            yield ac
    else:
        from asgi_lifespan import LifespanManager
        from app.main import create_app

        app = create_app()
        async with LifespanManager(app) as manager:
            transport = ASGITransport(app=manager.app)
            async with AsyncClient(transport=transport, base_url="http://testserver", timeout=60) as ac:
                ac.app = app  # type: ignore[attr-defined]
                yield ac


# ---------------------------------------------------------------------------
# JWT helpers
# ---------------------------------------------------------------------------

def _make_token(
    user_id: str = "test-user-001",
    office_id: str = "O_01",
    dept_id: str = "D_01",
    area_id: str = "A_01",
    role_ids: list[str] | None = None,
) -> str:
    """Generate a signed JWT for testing."""
    from jose import jwt as jose_jwt

    payload = {
        "sub": user_id,
        "office_id": office_id,
        "dept_id": dept_id,
        "area_id": area_id,
        "role_ids": role_ids or [],
    }
    return jose_jwt.encode(
        payload,
        settings.jwt_secret,
        algorithm=settings.jwt_algorithm,
    )


@pytest.fixture()
def auth_headers() -> dict[str, str]:
    """Admin-level auth headers (area leader: A_01, dept D_01, role R_01)."""
    token = _make_token(
        user_id="user_admin",
        office_id="O_01",
        dept_id="D_01",
        area_id="A_01",
        role_ids=["R_01"],
    )
    return {"Authorization": f"Bearer {token}"}


@pytest.fixture()
def user_headers() -> dict[str, str]:
    """Regular user auth headers (zhang_san: O_17, D_05)."""
    token = _make_token(
        user_id="user_001",
        office_id="O_17",
        dept_id="D_05",
        area_id="A_01",
        role_ids=[],
    )
    return {"Authorization": f"Bearer {token}"}


# ---------------------------------------------------------------------------
# PDF test file helpers
# ---------------------------------------------------------------------------

_TEST_FILES_DIR = Path(__file__).resolve().parent / "files"


@pytest.fixture(scope="session")
def example_pdf_path() -> Path:
    """Return path to a known example PDF file for ingest tests."""
    candidates = list(_TEST_FILES_DIR.glob("*.pdf"))
    if not candidates:
        pytest.skip("No example PDF files found in backend/tests/files/")
    # Pick the smallest file for speed
    return min(candidates, key=lambda p: p.stat().st_size)


@pytest.fixture(scope="session")
def all_example_pdfs() -> list[Path]:
    """Return paths to ALL example PDF files."""
    candidates = sorted(_TEST_FILES_DIR.glob("*.pdf"))
    if not candidates:
        pytest.skip("No example PDF files found in backend/tests/files/")
    return candidates


# ---------------------------------------------------------------------------
# Helpers exposed as fixtures
# ---------------------------------------------------------------------------

@pytest.fixture()
def make_token():
    """Factory fixture for creating JWT tokens with custom claims."""
    return _make_token


@pytest.fixture()
def api_prefix() -> str:
    """Return the v1 API path prefix."""
    return "/api/v1"
