"""
Fixtures per test di integrazione – DB in-memory SQLite + app FastAPI.

Per i test di integrazione si usa SQLite async invece di PostgreSQL per
semplicità e velocità. In CI si può aggiungere un servizio PostgreSQL reale.
"""

import os
import uuid
from typing import AsyncGenerator

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

# Override variabili d'ambiente prima di importare l'app
os.environ["ENCRYPTION_KEY"] = "b" * 64
os.environ["SECRET_KEY"] = "integration-test-secret-key-only-for-tests"
os.environ["DATABASE_URL"] = "sqlite+aiosqlite:///./test_integration.db"
os.environ["DATABASE_URL_SYNC"] = "sqlite:///./test_integration.db"
os.environ["APP_ENV"] = "development"
os.environ["APP_DEBUG"] = "false"

# ─── Compatibilità SQLite: tipi PostgreSQL non supportati ───────────────────
# SQLite non conosce INET, JSONB, ARRAY – mappiamo a tipi base compatibili.
# Deve essere eseguito PRIMA di importare i modelli ORM.
from sqlalchemy.dialects.sqlite import base as _sqlite_base


def _visit_inet(self, type_, **kw):      # noqa: ARG001
    return "VARCHAR(45)"

def _visit_jsonb(self, type_, **kw):     # noqa: ARG001
    return "JSON"

def _visit_array(self, type_, **kw):     # noqa: ARG001
    # SQLite non ha array nativi; usiamo TEXT (serializzato come JSON)
    return "TEXT"

def _visit_tsvector(self, type_, **kw):  # noqa: ARG001
    return "TEXT"

def _visit_tsquery(self, type_, **kw):   # noqa: ARG001
    return "TEXT"


_sqlite_base.SQLiteTypeCompiler.visit_INET = _visit_inet
_sqlite_base.SQLiteTypeCompiler.visit_JSONB = _visit_jsonb
_sqlite_base.SQLiteTypeCompiler.visit_ARRAY = _visit_array
_sqlite_base.SQLiteTypeCompiler.visit_TSVECTOR = _visit_tsvector
_sqlite_base.SQLiteTypeCompiler.visit_TSQUERY = _visit_tsquery
# ─────────────────────────────────────────────────────────────────────────────

from app.database import Base
from app.main import app

# Engine SQLite per test
TEST_DATABASE_URL = "sqlite+aiosqlite:///./test_integration.db"

test_engine = create_async_engine(
    TEST_DATABASE_URL,
    echo=False,
    connect_args={"check_same_thread": False},
)

# ─── Compatibilità SQLite: ARRAY come JSON in DML ────────────────────────────
# SQLite non può fare binding di Python list come parametri SQL;
# li convertiamo in JSON string prima dell'esecuzione.
import json as _json
from sqlalchemy import event as _sa_event


@_sa_event.listens_for(test_engine.sync_engine, "before_cursor_execute", retval=True)
def _sqlite_list_to_json(conn, cursor, statement, parameters, context, executemany):
    """Converte le liste Python in stringhe JSON per la compatibilità con SQLite."""
    def _convert(params):
        if isinstance(params, (list, tuple)):
            return type(params)(_json.dumps(v) if isinstance(v, list) else v for v in params)
        return params

    if executemany:
        return statement, [_convert(p) for p in parameters]
    return statement, _convert(parameters)
# ─────────────────────────────────────────────────────────────────────────────

TestAsyncSessionLocal = async_sessionmaker(
    bind=test_engine,
    class_=AsyncSession,
    expire_on_commit=False,
    autoflush=False,
)


@pytest_asyncio.fixture(scope="session", autouse=True)
async def setup_database():
    """Crea tutte le tabelle nel DB di test."""
    # Import modelli per registrarli nel metadata
    import app.models  # noqa: F401

    async with test_engine.begin() as conn:
        # SQLite non supporta tutti i tipi PostgreSQL, usiamo tabelle semplici
        await conn.run_sync(Base.metadata.drop_all)
        await conn.run_sync(Base.metadata.create_all)
    yield
    async with test_engine.begin() as conn:
        await conn.run_sync(Base.metadata.drop_all)
    await test_engine.dispose()
    # Pulisci file DB
    import os
    if os.path.exists("test_integration.db"):
        os.remove("test_integration.db")


@pytest_asyncio.fixture
async def db_session() -> AsyncGenerator[AsyncSession, None]:
    """Session DB isolata per ogni test (rollback automatico)."""
    async with TestAsyncSessionLocal() as session:
        yield session
        await session.rollback()


@pytest_asyncio.fixture
async def client(db_session: AsyncSession) -> AsyncGenerator[AsyncClient, None]:
    """HTTP client per test API con override del DB."""
    from app.database import get_db

    async def override_get_db():
        yield db_session

    app.dependency_overrides[get_db] = override_get_db

    async with AsyncClient(
        transport=ASGITransport(app=app),
        base_url="http://testserver",
    ) as c:
        yield c

    app.dependency_overrides.clear()


@pytest_asyncio.fixture
async def demo_tenant(db_session: AsyncSession):
    """
    Crea un tenant di test con ID e slug univoci per ogni test.

    Usiamo UUID randomici per evitare conflitti UNIQUE quando i test
    vengono eseguiti nello stesso processo e il commit non viene rollbackato.
    """
    from app.models.tenant import Tenant

    tenant_id = uuid.uuid4()
    tenant = Tenant(
        id=tenant_id,
        slug=f"test-{tenant_id.hex[:12]}",
        name="Test Tenant",
        plan="pro",
        max_mailboxes=10,
        max_users=10,
    )
    db_session.add(tenant)
    await db_session.commit()
    await db_session.refresh(tenant)
    return tenant


@pytest_asyncio.fixture
async def admin_user(db_session: AsyncSession, demo_tenant):
    """Crea un utente admin nel tenant di test."""
    from app.core.security import hash_password
    from app.models.user import User

    user = User(
        tenant_id=demo_tenant.id,
        email="admin@test.com",
        password_hash=hash_password("AdminPass1!"),
        full_name="Test Admin",
        role="admin",
        is_active=True,
    )
    db_session.add(user)
    await db_session.commit()
    await db_session.refresh(user)
    return user


@pytest_asyncio.fixture
async def admin_token(client: AsyncClient, admin_user, db_session: AsyncSession) -> str:
    """
    Token JWT per l'utente admin.
    Nota: il login usa il DB sovrapposto dalla fixture, quindi
    il seed_user deve già esistere.
    """
    from app.core.security import create_access_token
    token = create_access_token(
        subject=admin_user.id,
        tenant_id=admin_user.tenant_id,
        role=admin_user.role,
    )
    return token
