# app/core/soft_delete.py
"""
Soft-delete utility functions.

Provides helpers for soft-deleting, restoring, and cascade soft-deleting
records that use the SoftDeleteMixin.

Usage:
    from app.core.soft_delete import soft_delete, restore, soft_delete_cascade

    # Simple soft delete
    soft_delete(db, user, deleted_by_id=admin.id)

    # Cascade soft delete (merchant + all stores + their children)
    soft_delete_cascade(db, merchant, deleted_by_id=admin.id, cascade_rels=[
        ("stores", [("products", []), ("customers", []), ("orders", []), ("store_users", [])]),
    ])

    # Restore a soft-deleted record
    from app.modules.tenancy.models import User
    restore(db, User, entity_id=42, restored_by_id=admin.id)
"""

import logging
from datetime import UTC, datetime

from sqlalchemy import select
from sqlalchemy.orm import Session

logger = logging.getLogger(__name__)


def soft_delete(db: Session, entity, deleted_by_id: int | None = None) -> None:
    """
    Mark an entity as soft-deleted.

    Sets deleted_at to now and deleted_by_id to the actor.
    Does NOT call db.commit() — caller is responsible.

    Args:
        db: Database session.
        entity: SQLAlchemy model instance with SoftDeleteMixin.
        deleted_by_id: ID of the user performing the deletion.
    """
    entity.deleted_at = datetime.now(UTC)
    entity.deleted_by_id = deleted_by_id
    db.flush()

    logger.info(
        f"Soft-deleted {entity.__class__.__name__} id={entity.id} "
        f"by user_id={deleted_by_id}"
    )


def restore(
    db: Session,
    model_class,
    entity_id: int,
    restored_by_id: int | None = None,
):
    """
    Restore a soft-deleted entity.

    Queries with include_deleted=True to find the record, then clears
    deleted_at and deleted_by_id.

    Args:
        db: Database session.
        model_class: SQLAlchemy model class.
        entity_id: ID of the entity to restore.
        restored_by_id: ID of the user performing the restore (for logging).

    Returns:
        The restored entity.

    Raises:
        ValueError: If entity not found.
    """
    entity = db.execute(
        select(model_class).filter(model_class.id == entity_id),
        execution_options={"include_deleted": True},
    ).scalar_one_or_none()

    if entity is None:
        raise ValueError(f"{model_class.__name__} with id={entity_id} not found")

    if entity.deleted_at is None:
        raise ValueError(f"{model_class.__name__} with id={entity_id} is not deleted")

    entity.deleted_at = None
    entity.deleted_by_id = None
    db.flush()

    logger.info(
        f"Restored {model_class.__name__} id={entity_id} "
        f"by user_id={restored_by_id}"
    )
    return entity


def soft_delete_cascade(
    db: Session,
    entity,
    deleted_by_id: int | None = None,
    cascade_rels: list[tuple[str, list]] | None = None,
) -> int:
    """
    Soft-delete an entity and recursively soft-delete its children.

    Args:
        db: Database session.
        entity: SQLAlchemy model instance with SoftDeleteMixin.
        deleted_by_id: ID of the user performing the deletion.
        cascade_rels: List of (relationship_name, child_cascade_rels) tuples.
            Example: [("stores", [("products", []), ("customers", [])])]

    Returns:
        Total number of records soft-deleted (including the root entity).
    """
    count = 0

    # Soft-delete the entity itself
    soft_delete(db, entity, deleted_by_id)
    count += 1

    # Recursively soft-delete children
    if cascade_rels:
        for rel_name, child_cascade in cascade_rels:
            children = getattr(entity, rel_name, None)
            if children is None:
                continue

            # Handle both collections and single items (uselist=False)
            if not isinstance(children, list):
                children = [children]

            for child in children:
                if hasattr(child, "deleted_at") and child.deleted_at is None:
                    count += soft_delete_cascade(
                        db, child, deleted_by_id, child_cascade
                    )

    return count
