from __future__ import annotations

from collections.abc import Callable
from typing import Any, Literal

from sqlalchemy import inspect

ChangeOperation = Literal["created", "updated", "deleted"]


def object_state(obj: object) -> Any:
    return inspect(obj)


def has_attr_changes(state: Any, attrs: tuple[str, ...]) -> bool:
    return any(name in state.attrs and state.attrs[name].history.has_changes() for name in attrs)


def previous_value(obj: object, attr_name: str) -> str | None:
    state = object_state(obj)
    if attr_name not in state.attrs:
        return None
    history = state.attrs[attr_name].history
    if not history.has_changes() or not history.deleted:
        return None
    value = history.deleted[0]
    return str(value) if value is not None else None


def ensure_object_id(obj: object, new_id: Callable[[], str]) -> str:
    resource_id = getattr(obj, "id", None)
    if resource_id:
        return str(resource_id)
    resource_id = new_id()
    setattr(obj, "id", resource_id)
    return resource_id


def operation_for_object(obj: object, *, changed_attrs: tuple[str, ...]) -> ChangeOperation | None:
    state = object_state(obj)
    if state.pending:
        return "created"
    if state.deleted:
        return "deleted"
    if not has_attr_changes(state, changed_attrs):
        return None
    return "updated"


def operation_for_soft_deletable(
    obj: object,
    *,
    changed_attrs: tuple[str, ...],
    deleted_attr: str = "deleted_at",
) -> ChangeOperation | None:
    state = object_state(obj)
    if state.pending:
        return "created"
    if not has_attr_changes(state, changed_attrs):
        return None
    if deleted_attr in state.attrs:
        history = state.attrs[deleted_attr].history
        if history.has_changes():
            if any(value is not None for value in history.added):
                return "deleted"
            if any(value is not None for value in history.deleted):
                return "created"
    return "updated"
