"""Shared pytest fixtures.

Tests run against in-memory SQLite — no docker, no Postgres install.
The cross-dialect type variants in :mod:`app.models` keep the schema
identical in behavior for everything PR 1 exercises (the JSONB
column on ``gumroad_events`` isn't touched until PR 2).

Auth: a fixed test token is wired into the settings cache before any
app modules import, and the ``client`` fixture overrides the
``require_localhost`` guard since Starlette's TestClient connects
from a synthetic ``testclient`` peer.
"""

from __future__ import annotations

import os
import sys
from pathlib import Path

# Set required env BEFORE importing anything from app.* so pydantic
# Settings (lru_cache'd) picks up these values on first access.
os.environ["DATATOOLS_ADMIN_TOKEN"] = "test-admin-token"
os.environ["DATABASE_URL"] = "sqlite+pysqlite:///:memory:"

# Make the desktop license module importable as `datatools_license`.
# In the Docker image this happens via `COPY src/license /app/datatools_license`;
# during local tests we simulate it by aliasing src.license.
_REPO_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(_REPO_ROOT))
import src.license as _dt_license_module
sys.modules.setdefault("datatools_license", _dt_license_module)
for _sub in ("crypto", "schema", "features", "_dev_keypair"):
    sys.modules.setdefault(
        f"datatools_license.{_sub}",
        __import__(f"src.license.{_sub}", fromlist=[_sub]),
    )

import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine, event
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool

import app.db as app_db
from app.db import Base
from app.main import app


@pytest.fixture(scope="session")
def engine():
    eng = create_engine(
        "sqlite+pysqlite:///:memory:",
        connect_args={"check_same_thread": False},
        poolclass=StaticPool,
        future=True,
    )

    # Enforce foreign keys on SQLite (off by default).
    @event.listens_for(eng, "connect")
    def _fk_on(dbapi_conn, _):
        dbapi_conn.execute("PRAGMA foreign_keys=ON")

    Base.metadata.create_all(eng)
    yield eng
    eng.dispose()


@pytest.fixture(autouse=True)
def _bind_app_engine(engine, monkeypatch):
    """Point the app's session factory at the test engine and wipe rows
    between tests so order-of-execution can't leak state."""
    TestSession = sessionmaker(bind=engine, autoflush=False, autocommit=False, expire_on_commit=False)
    monkeypatch.setattr(app_db, "engine", engine)
    monkeypatch.setattr(app_db, "SessionLocal", TestSession)
    yield
    with engine.begin() as conn:
        for tbl in reversed(Base.metadata.sorted_tables):
            conn.execute(tbl.delete())


@pytest.fixture
def db_session(engine):
    """Per-test session with rollback isolation."""
    Session = sessionmaker(bind=engine, autoflush=False, autocommit=False, expire_on_commit=False)
    sess = Session()
    try:
        yield sess
    finally:
        # Clean rows between tests rather than transaction-rolling (the
        # mint code flushes mid-transaction and we want each test to
        # see a clean licenses table).
        sess.rollback()
        for tbl in reversed(Base.metadata.sorted_tables):
            sess.execute(tbl.delete())
        sess.commit()
        sess.close()


@pytest.fixture
def client():
    """Plain TestClient. Bearer-token check is live."""
    with TestClient(app) as c:
        yield c


@pytest.fixture
def admin_headers() -> dict[str, str]:
    return {"Authorization": "Bearer test-admin-token"}
