from __future__ import annotations

from flask import Blueprint, request

from editor.backend.utils.api import use_graph_metadata_store, use_graph_snapshot_store
from solutions.backend.utils import return_ko, return_ok
from solutions.graph.commands import add_edge_group as add_edge_group_cmd
from solutions.graph.commands import add_node_group as add_node_group_cmd
from solutions.graph.commands import create_graph as create_graph_cmd
from solutions.graph.commands import delete_edge_group as delete_edge_group_cmd
from solutions.graph.commands import delete_graph as delete_graph_cmd
from solutions.graph.commands import delete_node_group as delete_node_group_cmd
from solutions.graph.commands import rename_graph as rename_graph_cmd
from solutions.graph.commands import update_cypher_queries as update_cypher_queries_cmd
from solutions.graph.commands import update_edge_group as udpate_edge_group_cmd
from solutions.graph.commands import update_node_group as update_node_group_cmd
from solutions.graph.commands import update_sampling as update_sampling_cmd
from solutions.graph.commands.cypher_queries_update import Params as UpdateCypherQueries
from solutions.graph.commands.graph_deletion import Params as DeleteGraphParams
from solutions.graph.commands.graph_renaming import Params as RenameGraphParams
from solutions.graph.commands.group_addition import AddGroupResult
from solutions.graph.commands.group_deletion import EdgeParams as DeleteEdgeParams
from solutions.graph.commands.group_deletion import NodeParams as DeleteNodeParams
from solutions.graph.commands.group_mutation_models import AddOrUpdateEdgeParams, AddOrUpdateNodeParams
from solutions.graph.commands.group_update import UpdateGroupResult
from solutions.graph.commands.sampling_update import Params as UpdateSamplingParams
from solutions.graph.models import DeletingNodeWithDepedentEdgesError
from solutions.graph.store.graph_metadata_snapshot_store import GraphMetadataSnapshotStore
from solutions.graph.store.graph_metadata_store import GraphMetadataStore

graph_meta = Blueprint("graph_meta", __name__, url_prefix="/graph_meta")


@graph_meta.route("/rename_graph", methods=["POST"])
@use_graph_metadata_store
def rename_graph(graph_store: GraphMetadataStore):
    params = RenameGraphParams(**request.get_json())
    metadata = rename_graph_cmd(graph_store, params)
    return return_ok(data={"version_token": metadata["version_token"]})


@graph_meta.route("/<id>", methods=["GET"])
@use_graph_metadata_store
def get_metadata(graph_store: GraphMetadataStore, id: str):
    graph_metadata = graph_store.get(id)

    return return_ok(
        data={
            "graph_id": graph_metadata["id"],
            "graph_name": graph_metadata["name"],
            "version_token": graph_metadata["version_token"],
            "nodes": graph_metadata["nodes"],
            "edges": graph_metadata["edges"],
            "view_configuration": {
                "sampling": graph_metadata["sampling"]["sampling"],
                "max_rows": graph_metadata["sampling"]["max_rows"],
                "nodes": graph_metadata["nodes_view"],
                "edges": graph_metadata["edges_view"],
            },
            "cypher_queries": graph_metadata["cypher_queries"],
        }
    )


@graph_meta.route("/add_node", methods=["POST"])
@use_graph_metadata_store
def add_node(graph_store: GraphMetadataStore):
    params = AddOrUpdateNodeParams(**request.get_json())

    graph_id = params.graph_id
    result: AddGroupResult | UpdateGroupResult
    if params.meta.node_id:
        result = update_node_group_cmd(graph_store, params)
        return return_ok(
            {
                "graph_id": graph_id,
                "version_token": result["version_token"],
                "updated": result["metadata_changed"],
                "view_updated": result["view_config_changed"],
                "node_id": result["group_id"],
                "graph_meta": result["graph_meta"],
                "view_config": result["view_config"],
            }
        )
    else:
        result = add_node_group_cmd(graph_store, params)
        return return_ok(
            {
                "graph_id": graph_id,
                "version_token": result["version_token"],
                "updated": True,
                "view_updated": False,
                "node_id": result["group_id"],
                "graph_meta": result["graph_meta"],
                "edge_data": [],
                "view_config": result["view_config"],
            }
        )


@graph_meta.route("/delete_node", methods=["POST"])
@use_graph_metadata_store
def delete_node(graph_store: GraphMetadataStore):
    params = DeleteNodeParams(**request.get_json())

    try:
        version_token = delete_node_group_cmd(graph_store, params)["version_token"]

        return return_ok({"node_id": params.node_id, "version_token": version_token})
    except DeletingNodeWithDepedentEdgesError as ex:
        return return_ko({"errorCode": "DELETING_NODE_WITH_DEPENDENT_EDGES"})


@graph_meta.route("/add_edge", methods=["POST"])
@use_graph_metadata_store
def add_edge(graph_store: GraphMetadataStore):
    params = AddOrUpdateEdgeParams(**request.get_json())

    graph_id = params.graph_id
    result: AddGroupResult | UpdateGroupResult
    if params.meta.edge_id:
        result = udpate_edge_group_cmd(graph_store, params)
        return return_ok(
            {
                "graph_id": graph_id,
                "version_token": result["version_token"],
                "updated": result["metadata_changed"],
                "view_updated": result["view_config_changed"],
                "graph_meta": result["graph_meta"],
                "edge_id": result["group_id"],
                "view_config": result["view_config"],
            }
        )
    else:
        result = add_edge_group_cmd(graph_store, params)
        return return_ok(
            {
                "graph_id": graph_id,
                "version_token": result["version_token"],
                "updated": True,
                "view_updated": False,
                "graph_meta": result["graph_meta"],
                "edge_id": result["group_id"],
                "view_config": result["view_config"],
            }
        )


@graph_meta.route("/delete_edge", methods=["POST"])
@use_graph_metadata_store
def delete_edge(graph_store: GraphMetadataStore):
    params = DeleteEdgeParams(**request.get_json())

    version_token = delete_edge_group_cmd(graph_store, params)["version_token"]

    return return_ok({"updated": True, "edge_id": params.edge_id, "version_token": version_token})


@graph_meta.route("/create_new_graph", methods=["GET"])
@use_graph_metadata_store
def create_new_graph(graph_store: GraphMetadataStore):
    metadata = create_graph_cmd(graph_store)

    return return_ok(
        {
            "graph_id": metadata["id"],
            "graph_name": metadata["name"],
            "version_token": metadata["version_token"],
            "nodes": {},
            "edges": {},
            "view_configuration": {
                "sampling": metadata["sampling"]["sampling"],
                "max_rows": metadata["sampling"]["max_rows"],
                "nodes": {},
                "edges": {},
            },
            "cypher_queries": metadata["cypher_queries"],
        }
    )


@graph_meta.route("/delete_graph", methods=["POST"])
@use_graph_metadata_store
@use_graph_snapshot_store
def delete_graph(graph_store: GraphMetadataStore, snapshot_store: GraphMetadataSnapshotStore | None):
    params = DeleteGraphParams(**request.get_json())

    delete_graph_cmd(graph_store, snapshot_store, params)

    return return_ok()


@graph_meta.route("/update_sampling", methods=["POST"])
@use_graph_metadata_store
def update_sampling(graph_store: GraphMetadataStore):
    params = UpdateSamplingParams(**request.get_json())

    graph_id = params.graph_id
    (updated, metadata) = update_sampling_cmd(graph_store, params)

    return return_ok(
        {
            "updated": updated,
            "graph_id": graph_id,
            "version_token": metadata["version_token"],
            "sampling": params.sampling,
            "max_rows": params.max_rows,
        }
    )


@graph_meta.route("/cypherQueries", methods=["POST"])
@use_graph_metadata_store
def update_cypher_queries(graph_store: GraphMetadataStore):
    params = UpdateCypherQueries(**request.get_json())

    version_token = update_cypher_queries_cmd(graph_store, params)["version_token"]

    return return_ok({"version_token": version_token})


@graph_meta.route("/saved_graphs", methods=["GET"])
@use_graph_metadata_store
def saved_graphs(graph_store: GraphMetadataStore):
    graphs_metadata = graph_store.get_all()

    graphs = [{"graph_id": graph["id"], "graph_name": graph["name"]} for graph in graphs_metadata.values()]

    return return_ok(data={"saved_graphs": graphs})
