"""database."""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING

from sqlalchemy import inspect
from sqlalchemy.exc import NoInspectionAvailable

if TYPE_CHECKING:
    from collections.abc import Sequence

    from sqlalchemy.orm import Session

logger = logging.getLogger(__name__)


def safe_insert(orm_objects: Sequence[object], session: Session) -> list[tuple[Exception, object]]:
    """Safer insert at allows for partial rollbacks.

    Args:
        orm_objects (Sequence[object]): Tables to insert.
        session (Session): Database session.
    """
    if unmapped := [orm_object for orm_object in orm_objects if not _is_mapped_instance(orm_object)]:
        error = f"safe_insert expects ORM-mapped instances {unmapped}"
        raise TypeError(error)
    return _safe_insert(orm_objects, session)


def _safe_insert(objects: Sequence[object], session: Session) -> list[tuple[Exception, object]]:
    exceptions: list[tuple[Exception, object]] = []
    try:
        session.add_all(objects)
        session.commit()

    except Exception as error:
        session.rollback()

        objects_len = len(objects)
        if objects_len == 1:
            logger.exception(objects)
            return [(error, objects[0])]

        middle = objects_len // 2
        exceptions.extend(_safe_insert(objects=objects[:middle], session=session))
        exceptions.extend(_safe_insert(objects=objects[middle:], session=session))
    return exceptions


def _is_mapped_instance(obj: object) -> bool:
    """Return True if `obj` is a SQLAlchemy ORM-mapped instance."""
    try:
        inspect(obj)  # raises NoInspectionAvailable if not mapped
    except NoInspectionAvailable:
        return False
    else:
        return True
