from __future__ import annotations

import shortuuid
from typing_extensions import TypedDict

from solutions.graph.models import (
    EdgeGroupId,
    EdgeGroupMetadata,
    EdgeViewConfiguration,
    GraphMetadata,
    NodeGroupId,
    NodeGroupMetadata,
    NodeViewConfiguration,
    validate_edge_group,
    validate_node_group,
)
from stores.graph_metadata_store import GraphMetadataStore

from .group_mutation_models import (
    AddOrUpdateEdgeParams,
    AddOrUpdateNodeParams,
    to_edge_group_definition_metadata,
    to_node_group_definition_metadata,
)


class UpdateGroupResult(TypedDict):
    graph_meta: GraphMetadata
    group_id: NodeGroupId | EdgeGroupId
    version_token: str
    metadata_changed: bool
    view_config_changed: bool
    view_config: NodeViewConfiguration | EdgeViewConfiguration


def update_node_group(store: GraphMetadataStore, params: AddOrUpdateNodeParams) -> UpdateGroupResult:
    """
    Raises:
        ModelValidationError
        GraphDoesNotExistError
        ConcurrentUpdateCollisionError
        GraphMetadataStoreError
    """

    graph_id = params.graph_id
    graph_metadata = store.get(graph_id)

    # Map to model, validate and persist new configuration.
    node_id = params.meta.node_id
    assert node_id

    # Generate an id for new definitions.
    for definition in params.meta.definitions:
        if not definition.definition_id:
            definition.definition_id = shortuuid.uuid()[:6]

    node_group = params.meta.node_group
    node_group_definitions = to_node_group_definition_metadata(params.meta)
    node_group_metadata = NodeGroupMetadata(
        node_id=node_id,
        node_group=node_group,
        definitions=node_group_definitions,
    )
    node_view = NodeViewConfiguration(
        color=params.view_config.color, size=params.view_config.size, icon=params.view_config.icon
    )
    validate_node_group(graph_metadata, node_group_metadata)

    metadata_changed, view_config_changed = False, False
    if graph_metadata["nodes"][node_id] != node_group_metadata:
        metadata_changed = True
        graph_metadata["nodes"][node_id] = node_group_metadata

    if graph_metadata["nodes_view"][node_id] != node_view:
        view_config_changed = True
        graph_metadata["nodes_view"][node_id] = node_view

    new_version_token = params.ref_version_token
    if metadata_changed or view_config_changed:
        new_metadata = store.update(graph_metadata, params.ref_version_token)
        new_version_token = new_metadata["version_token"]

    return UpdateGroupResult(
        graph_meta=graph_metadata,
        version_token=new_version_token,
        group_id=node_id,
        metadata_changed=metadata_changed,
        view_config_changed=view_config_changed,
        view_config=node_view,
    )


def update_edge_group(store: GraphMetadataStore, params: AddOrUpdateEdgeParams) -> UpdateGroupResult:
    """
    Raises:
        ModelValidationError
        GraphDoesNotExistError
        ConcurrentUpdateCollisionError
        GraphMetadataStoreError
    """
    graph_id = params.graph_id
    graph_metadata = store.get(graph_id)

    # Map to model, validate and persist new configuration.
    edge_id = params.meta.edge_id
    assert edge_id

    # Generate an id for new definitions.
    for definition in params.meta.definitions:
        if not definition.definition_id:
            definition.definition_id = shortuuid.uuid()[:6]

    edge_group = params.meta.edge_group
    source_node_id = params.meta.source_node_id
    target_node_id = params.meta.target_node_id
    edge_group_metadata = EdgeGroupMetadata(
        edge_id=edge_id,
        edge_group=edge_group,
        source_node_id=source_node_id,
        target_node_id=target_node_id,
        definitions=to_edge_group_definition_metadata(params.meta),
    )
    edge_view = EdgeViewConfiguration(color=params.view_config.color, size=params.view_config.size)
    validate_edge_group(graph_metadata, edge_group_metadata)

    metadata_changed, view_config_changed = False, False
    if graph_metadata["edges"][edge_id] != edge_group_metadata:
        metadata_changed = True
        graph_metadata["edges"][edge_id] = edge_group_metadata

    if graph_metadata["edges_view"][edge_id] != edge_view:
        view_config_changed = True
        graph_metadata["edges_view"][edge_id] = edge_view

    new_version_token = params.ref_version_token
    if metadata_changed or view_config_changed:
        new_metadata = store.update(graph_metadata, params.ref_version_token)
        new_version_token = new_metadata["version_token"]

    return UpdateGroupResult(
        graph_meta=graph_metadata,
        version_token=new_version_token,
        group_id=edge_id,
        metadata_changed=metadata_changed,
        view_config_changed=view_config_changed,
        view_config=edge_view,
    )
