from typing import Any

import pytest
from sqlalchemy import create_engine
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import sessionmaker

import src.local_deep_research.utilities.db_utils as db_utils_module
from src.local_deep_research.web.database.models import Base
from src.local_deep_research.web.services.settings_manager import SettingsManager


@pytest.fixture(scope="session", autouse=True)
def setup_database_for_all_tests(
    tmp_path_factory, session_mocker
):  # Directly use the session_mocker provided by pytest-mock
    """
    Provides a database setup for a temporary SQLite file database for the entire test session.
    It patches db_utils.get_db_session and db_utils.get_settings_manager to use this test DB.
    """

    # Call cache_clear on the functions from db_utils_module.
    # This ensures any pre-existing cached instances are gone.
    # We must ensure db_utils_module is imported before this point.
    try:
        if hasattr(db_utils_module.get_db_session, "cache_clear"):
            db_utils_module.get_db_session.cache_clear()
        if hasattr(db_utils_module.get_settings_manager, "cache_clear"):
            db_utils_module.get_settings_manager.cache_clear()
        if hasattr(db_utils_module.get_db_setting, "cache_clear"):
            db_utils_module.get_db_setting.cache_clear()  # Clear get_db_setting's cache too

    except Exception as e:
        print(f"ERROR: Failed to clear db_utils caches aggressively: {e}")
        # This shouldn't prevent test run, but indicates a problem with cache_clear

    # Debug tmp_path_factory behavior
    temp_dir = tmp_path_factory.mktemp("db_test_data")
    db_file = temp_dir / "test_settings.db"
    db_url = f"sqlite:///{db_file}"

    engine = None
    try:
        engine = create_engine(db_url)
    except Exception as e:
        print(f"ERROR: Failed to create SQLAlchemy engine: {e}")
        raise

    try:
        Base.metadata.create_all(engine)
    except SQLAlchemyError as e:
        print(f"ERROR: SQLAlchemyError during Base.metadata.create_all: {e}")
        raise
    except Exception as e:
        print(f"ERROR: Unexpected error during Base.metadata.create_all: {e}")
        raise

    SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
    temp_session = SessionLocal()
    temp_settings_manager = SettingsManager(db_session=temp_session)

    try:
        temp_settings_manager.load_from_defaults_file(commit=True)
    except Exception as e:
        print(f"ERROR: Failed to load default settings: {e}")
        temp_session.rollback()  # Rollback if default loading fails
        raise  # Re-raise to fail the test if default loading is critical
    finally:
        temp_session.close()  # Close the temporary session used for loading defaults

    # Clear caches and patch
    db_utils_module.get_db_session.cache_clear()
    db_utils_module.get_settings_manager.cache_clear()

    mock_get_db_session = session_mocker.patch(
        "src.local_deep_research.utilities.db_utils.get_db_session"
    )
    mock_get_db_session.side_effect = SessionLocal

    mock_get_settings_manager = session_mocker.patch(
        "src.local_deep_research.utilities.db_utils.get_settings_manager"
    )
    mock_get_settings_manager.side_effect = lambda: SettingsManager(
        db_session=mock_get_db_session()
    )

    yield SessionLocal  # Yield the SessionLocal class for individual tests to create sessions

    if engine:
        engine.dispose()  # Dispose the engine to close all connections
    # tmp_path_factory handles deleting the temporary directory and its contents


@pytest.fixture
def mock_db_session(mocker):
    return mocker.MagicMock()


@pytest.fixture
def mock_logger(mocker):
    mocked_logger = mocker.patch(
        "src.local_deep_research.web.services.settings_manager.logger"
    )

    def _print_to_console(message: str, *args: Any) -> None:
        # Handle loguru formatting.
        message = message.format(*args)
        print(f"LOG: {message}")
        return mocker.DEFAULT

    # Pass through logged messages to the console.
    mocked_logger.debug = mocker.MagicMock(side_effect=_print_to_console)
    mocked_logger.info = mocker.MagicMock(side_effect=_print_to_console)
    mocked_logger.warning = mocker.MagicMock(side_effect=_print_to_console)
    mocked_logger.error = mocker.MagicMock(side_effect=_print_to_console)
    mocked_logger.critical = mocker.MagicMock(side_effect=_print_to_console)
    mocked_logger.exception = mocker.MagicMock(side_effect=_print_to_console)

    return mocked_logger
