import logging
from pathlib import Path
from typing import List, Optional

from dataiku import Folder

from backends.explorer.connections.db_config_scanner import DbConfigScanner
from backends.explorer.connections.db_handler_registry import get_handler_for_snapshot
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__)

PATH_TO_SNAPSHOT_JSON = "built-graphs/"
SNAPSHOT_JSON_FILE_NAME = "configuration.json"
BUILD_INFO_JSON_FILE_NAME = "buildInfo.json"


class ExplorerMetadataManager:
    def __init__(self, db_folders: List[Folder]):
        self.db_config_scanner = DbConfigScanner(db_folders)

    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:
            db_type = self.db_config_scanner.get_db_type(snapshot_id)
            if db_type == "kuzu":
                graph_metadata = KuzuMetadataBuilder(db_handler, snapshot_id).build()
            elif db_type == "neo4j":
                graph_metadata = Neo4jMetadataBuilder(db_handler, snapshot_id).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.
        """
        snapshot_list = self.db_config_scanner.get_snapshot_ids()

        snapshots = []
        for snapshot_id in snapshot_list:
            db_handler = get_handler_for_snapshot(snapshot_id, self.db_config_scanner.db_folders)
            snapshots.append(self.get_snapshot_metadata(snapshot_id, db_handler))

        return snapshots

    def _get_snapshot_metadata_from_configuration_file(self, snapshot_id: str) -> Optional[ExplorerMetadata]:
        """
        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.
        """
        relative_path_to_db = Path(PATH_TO_SNAPSHOT_JSON) / snapshot_id / SNAPSHOT_JSON_FILE_NAME
        build_info_path_to_db = Path(PATH_TO_SNAPSHOT_JSON) / snapshot_id / BUILD_INFO_JSON_FILE_NAME
        db_folder_containing_snapshot = self.db_config_scanner.get_db_folder_by_snapshot_id(snapshot_id)
        db_type = self.db_config_scanner.get_db_type(snapshot_id)
        try:
            snapshot_data = db_folder_containing_snapshot.read_json(relative_path_to_db)
            build_info_data = db_folder_containing_snapshot.read_json(build_info_path_to_db)
            return ExplorerMetadata(
                snapshot_id=snapshot_data["id"],
                db_type=db_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
