from collections.abc import Generator
from contextlib import contextmanager
from urllib.parse import quote_plus

from pymysql import OperationalError
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker

from app.config import Settings, settings
from app.log import get_logger

logger = get_logger(__name__)


def build_sqlalchemy_database_url_from_settings(_settings: Settings) -> str:
    """
    Build a SQLAlchemy URL based on the provided settings.

    Parameters
    ----------
        _settings (Settings): An instance of the Settings class
        containing the PostgreSQL connection details.

    Returns
    -------
        str: The generated SQLAlchemy URL.
    """
    DB_HOST = _settings.SQL_HOST
    DB_PORT = _settings.SQL_PORT
    DB_NAME = _settings.SQL_DB
    DB_USER = _settings.SQL_USER
    DB_PASS = _settings.SQL_PASS
    encoded_password = quote_plus(DB_PASS)
    if not DB_PASS:
        database_url = f"mysql+pymysql://{DB_USER}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
    else:
        database_url = f"mysql+pymysql://{DB_USER}:{encoded_password}@{DB_HOST}:{DB_PORT}/{DB_NAME}"

    return database_url


SQLALCHEMY_DATABASE_URL = build_sqlalchemy_database_url_from_settings(settings)
print(SQLALCHEMY_DATABASE_URL)
_engine = create_engine(
    SQLALCHEMY_DATABASE_URL,
    future=True,
    pool_recycle=3600,
)
_session_local = sessionmaker(autocommit=False, autoflush=False, bind=_engine)


def get_db() -> Generator[Session, None, None]:
    """
    Get the database session.

    Returns
    -------
        db: The database session.

    Raises
    ------
        OperationalError: If an error occurs while accessing the database.
    """
    db = _session_local()
    try:
        yield db
    except OperationalError as e:
        error_message = f"An error occurred while getting the database session. Error: {e!s}"
        logger.exception(error_message)
        raise  # Re-raise the exception for proper error handling outside the context manager
    finally:
        db.close()


@contextmanager
def get_ctx_db() -> Generator[Session, None, None]:
    """
    Context manager that creates a database session and yields
    it for use in a 'with' statement.

    Yields
    ------
    Session: A database session.
    """
    db = _session_local()  # Assuming _session_local() creates a new session
    try:
        yield db
    except Exception as e:
        error_message = f"An error occurred while getting the database session. Error: {e!s}"
        logger.exception(error_message)
        raise  # Re-raise the exception for proper error handling outside the context manager
    finally:
        db.close()  # Close the database session
