from __future__ import annotations

from collections.abc import Callable
from functools import wraps
from typing import Any, TypeVar

from backends.explorer.discovery.db_handler_registry import get_handler_for_snapshot
from backends.explorer.discovery.info_registry import ConnectionInfoRegistry, get_connection_info_registry
from backends.explorer.utils.presets import get_neo4j_preset_from_webapp
from backends.explorer.utils.webapp_config import webapp_config
from graph.connections.db_handler import DbHandler

R = TypeVar("R")


def get_webapp_connection_registry() -> ConnectionInfoRegistry:
    """
    Retrieves the connection info registry for the explorer webapp.

    The registry is built using the database folders, external Neo4j databases,
    and the Neo4j preset configured for the webapp.

    Returns:
        ConnectionInfoRegistry: The initialized connection info registry.
    """
    db_folders = webapp_config.db_explorer_folders
    neo4j_external_databases = webapp_config.neo4j_external_databases
    neo4j_preset = get_neo4j_preset_from_webapp()
    return get_connection_info_registry(db_folders, neo4j_external_databases, neo4j_preset)


def with_db_instance(f: Callable[..., R]) -> Callable[..., R]:
    """
    Decorator that injects a read-only database instance into the decorated function.

    The decorator extracts 'snapshot_id' from the function's keyword arguments,
    retrieves the corresponding database handler, and opens a new read-only
    database instance. The instance is passed as the first argument to the
    decorated function.

    Args:
        f (callable): The function to decorate.

    Returns:
        callable: The decorated function.

    Raises:
        ValueError: If 'snapshot_id' is missing from the arguments.
    """
    @wraps(f)
    def decorated_function(*args: Any, **kwargs: Any) -> R:
        snapshot_id = kwargs.get("snapshot_id")
        if not isinstance(snapshot_id, str) or not snapshot_id:
            raise ValueError("snapshot_id is required")

        db_handler, _ = get_webapp_db_handler(snapshot_id)

        with db_handler.get_new_db_instance(readonly=True) as db_instance:
            return f(db_instance, *args, **kwargs)  # type: ignore[arg-type,no-any-return]
    return decorated_function


def get_webapp_db_handler(snapshot_id: str) -> tuple[DbHandler, ConnectionInfoRegistry]:
    """
    Retrieves both the database handler and the connection registry for a snapshot.

    Args:
        snapshot_id (str): The unique identifier of the snapshot.

    Returns:
        tuple: A tuple containing (DbHandler, ConnectionInfoRegistry).
    """
    conn_info_registry = get_webapp_connection_registry()
    db_handler = get_handler_for_snapshot(snapshot_id, conn_info_registry)
    return db_handler, conn_info_registry


def with_db_handler(f: Callable[..., R]) -> Callable[..., R]:
    """
    Decorator that injects the database handler and connection registry into the function.

    The decorator extracts 'snapshot_id' from the function's keyword arguments,
    retrieves the database handler and connection registry, and passes them as the
    first two arguments to the decorated function.

    Args:
        f (callable): The function to decorate.

    Returns:
        callable: The decorated function.

    Raises:
        ValueError: If 'snapshot_id' is missing from the arguments.
    """
    @wraps(f)
    def decorated_function(*args: Any, **kwargs: Any) -> R:
        snapshot_id = kwargs.get("snapshot_id")
        if not isinstance(snapshot_id, str) or not snapshot_id:
            raise ValueError("snapshot_id is required")

        db_handler, conn_info_registry = get_webapp_db_handler(snapshot_id)

        return f(db_handler, conn_info_registry, *args, **kwargs)  # type: ignore[arg-type,no-any-return]
    return decorated_function
