# conftest.py - PostgreSQL test configuration (project root)
"""
Core pytest configuration and fixtures.

This project uses PostgreSQL for testing. Start the test database with:
    make test-db-up

IMPORTANT - Fixture Best Practices:
===================================
1. DO NOT use db.expunge() on fixtures - it detaches objects from the session
   and breaks lazy loading of relationships (e.g., product.marketplace_product).

2. Test isolation is achieved through TRUNCATE CASCADE after each test,
   which is much faster than dropping/recreating tables.

3. If you need to ensure an object has fresh data, use db.refresh(obj) instead
   of expunge/re-query patterns.

4. The session uses expire_on_commit=False to prevent objects from being expired
   after commits, which helps with relationship access across operations.

See docs/testing/testing-guide.md for comprehensive testing documentation.
"""

import os

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

from app.core.database import Base, get_db
from main import app

# PostgreSQL test database URL
# Use environment variable or default to local Docker test database
TEST_DATABASE_URL = os.getenv(
    "TEST_DATABASE_URL",
    "postgresql://test_user:test_password@localhost:5433/orion_test"
)


@pytest.fixture(scope="session")
def engine():
    """Create test database engine.

    Verifies PostgreSQL connection on startup and provides helpful
    error message if the test database is not running.
    """
    engine = create_engine(
        TEST_DATABASE_URL,
        pool_pre_ping=True,
        echo=False,  # Set to True for SQL debugging
    )

    # Verify connection on startup
    try:
        with engine.connect() as conn:
            conn.execute(text("SELECT 1"))
    except Exception as e:
        pytest.exit(
            f"\n\nCannot connect to test database at {TEST_DATABASE_URL}\n"
            f"Error: {e}\n\n"
            "Start the test database with:\n"
            "    make test-db-up\n\n"
            "Or manually:\n"
            "    docker-compose -f docker-compose.test.yml up -d\n"
        )

    return engine


@pytest.fixture(scope="session")
def testing_session_local(engine):
    """
    Create session factory for tests.

    Uses expire_on_commit=False to prevent objects from being expired after
    commits. This allows fixtures to remain usable after database operations
    without needing to refresh or re-query them.
    """
    from app.core.database import register_soft_delete_filter

    session_factory = sessionmaker(
        autocommit=False,
        autoflush=False,
        bind=engine,
        expire_on_commit=False,  # Prevents lazy-load issues after commits
    )
    register_soft_delete_filter(session_factory)
    return session_factory


@pytest.fixture(scope="session", autouse=True)
def setup_database(engine):
    """Create all tables once at the start of the test session."""
    with engine.connect() as conn:
        # Terminate any other connections to avoid deadlocks
        conn.execute(text("""
            SELECT pg_terminate_backend(pid)
            FROM pg_stat_activity
            WHERE datname = current_database() AND pid <> pg_backend_pid()
        """))
        conn.execute(text("DROP SCHEMA public CASCADE"))
        conn.execute(text("CREATE SCHEMA public"))
        conn.commit()
    Base.metadata.create_all(bind=engine)
    yield


@pytest.fixture(scope="function")
def db(engine, testing_session_local):
    """
    Create a database session for each test function.

    Provides test isolation by:
    - Using a fresh session for each test
    - Truncating all tables after each test (fast cleanup)

    Note: Fixtures should NOT use db.expunge() as this detaches objects
    from the session and breaks lazy loading. The TRUNCATE provides
    sufficient isolation between tests.
    """
    db_session = testing_session_local()

    try:
        yield db_session
    finally:
        db_session.close()
        # Fast cleanup with TRUNCATE CASCADE
        with engine.connect() as conn:
            # Disable FK checks temporarily for fast truncation
            conn.execute(text("SET session_replication_role = 'replica'"))
            for table in reversed(Base.metadata.sorted_tables):
                conn.execute(text(f'TRUNCATE TABLE "{table.name}" CASCADE'))  # noqa: SEC011
            conn.execute(text("SET session_replication_role = 'origin'"))
            conn.commit()


@pytest.fixture(scope="function")
def client(db):
    """Create a test client with database dependency override"""

    # Override the dependency to use our test database
    def override_get_db():
        try:
            yield db
        finally:
            pass  # Don't close here, the db fixture handles it

    app.dependency_overrides[get_db] = override_get_db

    try:
        client = TestClient(app)
        yield client
    finally:
        # Clean up the dependency override
        if get_db in app.dependency_overrides:
            del app.dependency_overrides[get_db]


# Cleanup fixture to ensure clean state
@pytest.fixture(autouse=True)
def cleanup():
    """Automatically clean up after each test"""
    yield
    # Clear any remaining dependency overrides
    app.dependency_overrides.clear()


# Import fixtures from fixture modules
pytest_plugins = [
    "tests.fixtures.admin_platform_fixtures",
    "tests.fixtures.auth_fixtures",
    "tests.fixtures.loyalty_fixtures",
    "tests.fixtures.marketplace_product_fixtures",
    "tests.fixtures.store_fixtures",
    "tests.fixtures.customer_fixtures",
    "tests.fixtures.marketplace_import_job_fixtures",
    "tests.fixtures.message_fixtures",
    "tests.fixtures.testing_fixtures",
    "tests.fixtures.content_page_fixtures",
    "tests.fixtures.merchant_domain_fixtures",
]
