"""
Shared pytest fixtures for the state-hub API test suite.

Uses a real PostgreSQL test database (custodian_test) — never mocked.
Set TEST_DATABASE_URL to override the default.

Schema is created/dropped once per session via psycopg2 (synchronous) to
avoid asyncpg "another operation is in progress" errors during create_all.
Tables are truncated between tests for isolation.
"""
from __future__ import annotations

import os
import sys
from pathlib import Path

import pytest
import pytest_asyncio
import sqlalchemy
from httpx import ASGITransport, AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine

# Make api/ importable when running pytest from state-hub/
sys.path.insert(0, str(Path(__file__).parent.parent))

_ASYNC_URL = os.getenv(
    "TEST_DATABASE_URL",
    "postgresql+asyncpg://custodian:changeme@127.0.0.1:5432/custodian_test",
)
_SYNC_URL = _ASYNC_URL.replace("+asyncpg", "+psycopg2")

# ---------------------------------------------------------------------------
# Schema lifecycle (synchronous — avoids asyncpg concurrent-query errors)
# ---------------------------------------------------------------------------

@pytest.fixture(scope="session", autouse=True)
def _schema():
    """Create all tables before the session; drop them after."""
    # Import all models so metadata is fully populated
    from api.models import Base  # noqa: F401

    engine = sqlalchemy.create_engine(_SYNC_URL)
    Base.metadata.create_all(engine)
    yield
    Base.metadata.drop_all(engine)
    engine.dispose()


@pytest.fixture(autouse=True)
def _truncate(_schema):
    """Truncate all tables after each test for isolation."""
    from api.models import Base
    import api.routers.state as _state_router
    import api.routers.workstreams as _ws_router
    from api.services.summary_cache import reset_summary_cache_for_tests

    # Reset in-process caches so stale data from a previous test can't bleed through.
    reset_summary_cache_for_tests()
    _state_router._OVERVIEW_CACHE = None
    _state_router._OVERVIEW_CACHE_AT = 0.0
    _ws_router._INDEX_CACHE = None
    _ws_router._INDEX_CACHE_AT = 0.0
    _ws_router._INDEX_REFRESH_TASK = None
    _ws_router._INDEX_LAST_ERROR = None

    yield
    engine = sqlalchemy.create_engine(_SYNC_URL)
    with engine.begin() as conn:
        for table in reversed(Base.metadata.sorted_tables):
            conn.execute(table.delete())
    engine.dispose()


# ---------------------------------------------------------------------------
# Async engine (function-scoped, shared via session factory)
# ---------------------------------------------------------------------------

@pytest_asyncio.fixture
async def test_engine(_schema):
    engine = create_async_engine(_ASYNC_URL, echo=False)
    yield engine
    await engine.dispose()


# ---------------------------------------------------------------------------
# HTTP client
# ---------------------------------------------------------------------------

@pytest_asyncio.fixture
async def client(test_engine):
    """AsyncClient backed by the FastAPI app with get_session overridden."""
    from api.database import get_session
    from api.main import app

    factory = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False)

    async def _override():
        async with factory() as session:
            yield session

    from api.services import write_idempotency as _write_idempotency

    old_session_factory = _write_idempotency.async_session_factory
    _write_idempotency.async_session_factory = factory
    app.dependency_overrides[get_session] = _override
    try:
        async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac:
            yield ac
    finally:
        app.dependency_overrides.clear()
        _write_idempotency.async_session_factory = old_session_factory


# ---------------------------------------------------------------------------
# Shared entity helpers (workplan-first; legacy workstream names retained)
# ---------------------------------------------------------------------------

async def create_test_domain(client, slug="infotech", name="Infotech"):
    r = await client.post("/domains/", json={"slug": slug, "name": name})
    assert r.status_code == 201, r.text
    return r.json()


async def create_test_topic(client, domain_slug="infotech", slug="testtopic", title="Test Topic"):
    r = await client.post("/topics/", json={
        "slug": slug, "title": title, "domain": domain_slug,
    })
    assert r.status_code == 201, r.text
    return r.json()


async def create_test_repo(client, domain_slug="infotech", slug="test-repo", **extra):
    payload = {
        "domain_slug": domain_slug,
        "slug": slug,
        "name": "Test Repo",
        **extra,
    }
    r = await client.post("/repos/", json=payload)
    assert r.status_code == 201, r.text
    return r.json()


async def create_test_workplan(
    client,
    repo_id,
    topic_id=None,
    slug="test-wp",
    title="Test Workplan",
    status="active",
    **extra,
):
    payload = {"repo_id": repo_id, "slug": slug, "title": title, "status": status, **extra}
    if topic_id is not None:
        payload["topic_id"] = topic_id
    r = await client.post("/workplans/", json=payload)
    assert r.status_code == 201, r.text
    return r.json()


async def create_test_workstream(client, topic_id=None, repo_id=None, slug="test-wp", **kwargs):
    """Legacy helper name — creates a repo-anchored workplan."""
    if repo_id is None:
        domain = await create_test_domain(client)
        if topic_id is None:
            topic = await create_test_topic(client, domain_slug=domain["slug"])
            topic_id = topic["id"]
        repo = await create_test_repo(client, domain_slug=domain["slug"], slug=f"{slug}-repo")
        repo_id = repo["id"]
    return await create_test_workplan(client, repo_id=repo_id, topic_id=topic_id, slug=slug, **kwargs)
