from collections.abc import Generator

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

# Importar modelos con relaciones string para registrar mappers antes de tests.
# Evita errores tipo: expression 'Pedimentos' failed to locate a name.
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos  # noqa: F401
from api.v1.modules.a76.invoices.models import InvoiceHeader
from api.v1.modules.a76.transportation.trailers.models import Trailer  # noqa: F401
from api.v1.modules.a76.transportation.transporters.models import Transporter  # noqa: F401
from api.v1.modules.a76.transportation.vehicles.models import Vehicle  # noqa: F401
from api.v1.modules.a76.invoices.imports.process import routes as process_routes
from api.v1.modules.a76.invoices.imports.process import main_process as import_main_process
from api.v1.modules.a76.invoices.exports.process import main_process as export_main_process
from core.celery_app import celery_app
from core.config import settings
from core.database import get_core_db
from core.security import get_current_user
from tests.fixtures.builders import (
    EphemeralOrgIds,
    allocate_ephemeral_tenant_company_ids,
    ensure_tenant_company,
)


TEST_DB_URL = (
    __import__("os").environ.get("TEST_DATABASE_URL")
    or __import__("os").environ.get("CORE_DATABASE_URL")
    or settings.core_database_url
)

engine = create_engine(TEST_DB_URL, future=True)
TestingSessionLocal = sessionmaker(
    bind=engine,
    autoflush=False,
    autocommit=False,
    expire_on_commit=False,
    class_=Session,
    join_transaction_mode="create_savepoint",
)


@pytest.fixture
def db_session() -> Generator[Session, None, None]:
    connection = engine.connect()
    transaction = connection.begin()
    session = TestingSessionLocal(bind=connection)
    try:
        yield session
    finally:
        session.close()
        transaction.rollback()
        connection.close()


@pytest.fixture
def test_tenant(db_session: Session) -> EphemeralOrgIds:
    """Tenant y company dedicados por test (IDs efímeros en rango alto)."""
    tid, cid = allocate_ephemeral_tenant_company_ids(db_session)
    ensure_tenant_company(db_session, tenant_id=tid, company_id=cid)
    return EphemeralOrgIds(tenant_id=tid, company_id=cid)


@pytest.fixture(autouse=True)
def celery_eager() -> Generator[None, None, None]:
    prev_broker = celery_app.conf.broker_url
    prev_backend = celery_app.conf.result_backend
    prev_always_eager = celery_app.conf.task_always_eager
    prev_propagates = celery_app.conf.task_eager_propagates
    prev_store_result = celery_app.conf.task_store_eager_result
    prev_ignore_result = celery_app.conf.task_ignore_result

    # Aislar Celery de infraestructura externa en tests (sin Redis/Valkey).
    celery_app.conf.broker_url = "memory://"
    celery_app.conf.result_backend = "cache+memory://"
    celery_app.conf.task_always_eager = True
    celery_app.conf.task_eager_propagates = True
    celery_app.conf.task_store_eager_result = False
    celery_app.conf.task_ignore_result = True
    try:
        yield
    finally:
        celery_app.conf.broker_url = prev_broker
        celery_app.conf.result_backend = prev_backend
        celery_app.conf.task_always_eager = prev_always_eager
        celery_app.conf.task_eager_propagates = prev_propagates
        celery_app.conf.task_store_eager_result = prev_store_result
        celery_app.conf.task_ignore_result = prev_ignore_result


@pytest.fixture
def app(
    db_session: Session,
    test_tenant: EphemeralOrgIds,
    monkeypatch: pytest.MonkeyPatch,
) -> FastAPI:
    test_app = FastAPI()
    test_app.include_router(process_routes.router, prefix="/api/v1/a76")

    def _override_get_db() -> Generator[Session, None, None]:
        yield db_session

    async def _override_current_user():
        return {"sub": "test-user", "tenant_id": test_tenant.tenant_id}

    # validate_access_to_resource is imported directly in the routes module.
    def _override_validate_access_to_resource(db, company_id, current_user, required_permissions=None, **kwargs):
        return int(current_user["tenant_id"])

    monkeypatch.setattr(process_routes, "validate_access_to_resource", _override_validate_access_to_resource)

    class _InlineResult:
        def __init__(self, task_id: str):
            self.id = task_id

    def _run_import_inline(*, args=None, **kwargs):
        if args is None:
            args = []
        invoice_id, tenant_id, company_id = args
        invoice = db_session.get(InvoiceHeader, int(invoice_id))
        if invoice is None:
            return _InlineResult("missing-import-invoice")
        import_main_process.main_process(db_session, invoice, str(tenant_id), str(company_id))
        db_session.flush()
        return _InlineResult(f"inline-import-{invoice_id}")

    def _run_export_inline(*, args=None, **kwargs):
        if args is None:
            args = []
        invoice_id, tenant_id, company_id = args
        invoice = db_session.get(InvoiceHeader, int(invoice_id))
        if invoice is None:
            return _InlineResult("missing-export-invoice")
        export_main_process.main_process(db_session, invoice, str(tenant_id), str(company_id))
        db_session.flush()
        return _InlineResult(f"inline-export-{invoice_id}")

    class _InlineTask:
        def __init__(self, runner):
            self.apply_async = runner

    # Evita worker/redis y obliga ejecución inline con la misma sesión.
    monkeypatch.setattr(process_routes, "process_invoice_task", _InlineTask(_run_import_inline))
    monkeypatch.setattr(process_routes, "process_export_invoice_task", _InlineTask(_run_export_inline))

    test_app.dependency_overrides[get_core_db] = _override_get_db
    test_app.dependency_overrides[get_current_user] = _override_current_user
    return test_app


@pytest.fixture
def client(app: FastAPI) -> TestClient:
    return TestClient(app)
