from __future__ import annotations

import logging

from backends.explorer.discovery.db_handler_registry import get_handler_for_snapshot
from backends.explorer.discovery.info_registry import ConnectionInfoRegistry
from backends.explorer.metadata.kuzu.metadata_builder import KuzuMetadataBuilder
from backends.explorer.metadata.neo4j.metadata_builder import Neo4jMetadataBuilder
from graph.connections.db_handler import DbHandler
from solutions.graph.models import EdgeGroupDefinition, ExplorerMetadata, NodeGroupDefinition

logger = logging.getLogger(__name__)


class ExplorerMetadataManager:
    def __init__(self, conn_info_registry: ConnectionInfoRegistry):
        self.conn_info_registry = conn_info_registry

    def get_snapshot_metadata(self, snapshot_id: str, db_handler: DbHandler) -> ExplorerMetadata:
        """
        Retrieves the metadata for a given saved configuration.

        If the metadata for the specified saved configuration ID is already available, it is returned directly.
        Otherwise, a new database instance is created for the saved configuration, and the metadata is built using
        the ExplorerMetadataBuilder.

        Args:
            snapshot_id (str): The unique identifier of the saved configuration for which metadata is requested.

        Returns:
            ExplorerMetadata: The metadata associated with the specified saved configuration.
        """

        if (graph_metadata := self._get_snapshot_metadata_from_configuration_file(snapshot_id)) is None:
            conn_info = self.conn_info_registry.get_connection_info(snapshot_id)
            if not conn_info:
                raise ValueError(f"No connection info found for snapshot {snapshot_id}")
        
            if conn_info.type == "kuzu":
                graph_metadata = KuzuMetadataBuilder(db_handler, conn_info).build()
            elif conn_info.type == "neo4j":
                graph_metadata = Neo4jMetadataBuilder(db_handler, conn_info).build()
            else:
                raise ValueError(f"Unknown database type for snapshot {snapshot_id}")

        return graph_metadata

    def get_snapshots_metadata(self) -> list[ExplorerMetadata]:
        """
        Retrieves metadata for all available saved configurations in the database folder.

        This method scans the database folder for file paths containing "/db/", extracts unique saved configuration IDs from those paths,
        and then gathers metadata for each identified saved configuration.

        Returns:
            List[ExplorerMetadata]: A list of metadata objects corresponding to each discovered saved configuration.
        """
        conn_info_list = self.conn_info_registry.list_connection_info()
        
        snapshots = []
        for conn_info in conn_info_list:
            if not conn_info.db_folder:
                logger.warning(f"No database folder found for snapshot {conn_info.uid}")

            db_handler = get_handler_for_snapshot(conn_info.uid, self.conn_info_registry)
            metadata = self.get_snapshot_metadata(conn_info.uid, db_handler)
            snapshots.append(metadata)

        return snapshots

    def _get_snapshot_metadata_from_configuration_file(self, snapshot_id: str) -> ExplorerMetadata | None:
        """
        Retrieves and parses saved configuration metadata from a JSON file for a given saved configuration ID.

        The method constructs the path to the saved configuration JSON file, reads its contents, and extracts
        relevant metadata fields such as saved configuration ID, name, comment, epoch timestamp, node and edge
        definitions, view configurations, and cypher queries. If any error occurs during reading or
        parsing, the method returns None.

        Args:
            snapshot_id (str): The unique identifier of the saved configuration to retrieve.

        Returns:
            Optional[ExplorerMetadata]: An ExplorerMetadata object containing the parsed saved configuration data
            if successful, or None if the data could not be retrieved or parsed.
        """
        conn_info = self.conn_info_registry.get_connection_info(snapshot_id)
        if not conn_info:
            raise ValueError(f"No connection info found for snapshot {snapshot_id}")
        if not conn_info.db_folder:
            logger.warning(f"No database folder found for snapshot {snapshot_id}")
            return None

        try:
            snapshot_data = conn_info.get_configuration()
            build_info_data = conn_info.get_build_info()
            if not snapshot_data or not build_info_data:
                raise ValueError(f"No configuration or build info found for snapshot {snapshot_id}")

            return ExplorerMetadata(
                snapshot_id=snapshot_data["id"],
                db_type=conn_info.type,
                name=snapshot_data.get("name", ""),
                comment=snapshot_data.get("comment", ""),
                epoch_ms=build_info_data.get("epoch_ms", 0),
                node_definitions=[
                    NodeGroupDefinition(
                        node_group=node["node_group"],
                        node_id=node["node_id"],
                        definition_id=node_def["definition_id"],
                        source_dataset=node_def["source_dataset"],
                        primary_col=node_def["primary_col"],
                        label_col=node_def["label_col"],
                        property_list=node_def["property_list"],
                        filters_association=node_def["filters_association"],
                        filters_stored=node_def["filters_stored"],
                    )
                    for node in snapshot_data.get("nodes", {}).values()
                    for node_def in node.get("definitions", [])
                ],
                edge_definitions=[
                    EdgeGroupDefinition(
                        source_node_group=snapshot_data["nodes"][edge["source_node_id"]]["node_group"],
                        target_node_group=snapshot_data["nodes"][edge["target_node_id"]]["node_group"],
                        source_node_id=edge["source_node_id"],
                        target_node_id=edge["target_node_id"],
                        edge_group=edge["edge_group"],
                        edge_id=edge["edge_id"],
                        definition_id=edge_def["definition_id"],
                        edge_dataset=edge_def["edge_dataset"],
                        source_column=edge_def["source_column"],
                        target_column=edge_def["target_column"],
                        property_list=edge_def["property_list"],
                        filters_association=edge_def["filters_association"],
                        filters_stored=edge_def["filters_stored"],
                    )
                    for edge in snapshot_data.get("edges", {}).values()
                    for edge_def in edge.get("definitions", [])
                ],
                nodes_view=snapshot_data.get("nodes_view", {}),
                edges_view=snapshot_data.get("edges_view", {}),
                cypher_queries=snapshot_data.get("cypher_queries", []),
            )
        except Exception as e:
            logger.error(f"Error reading saved configuration metadata file {str(e)}")
            return None
