"""
DataIdFormat - Formatting functions for converting graph data to JSON.

This module implements the three-pass formatting process for converting
concepts and connections into nested JSON structures.

The format produces output like:
[
    {
        "id": 123,
        "data": {
            "the_person": {
                "the_name": {"id": 1, "data": {"the_name": "John"}},
                "the_email": {"id": 2, "data": {"the_email": "john@example.com"}}
            }
        },
        "created_on": "2024-01-01T00:00:00"
    }
]
"""

from typing import Any, Dict, List, Optional
from ccs.models.connection import Connection
from ccs.services.search.count_info import CountInfo


async def format_connections_data_id(
    linkers: List[int],
    concept_ids: List[int],
    main_composition_ids: List[int],
    reverse: List[int],
    count_infos: List[CountInfo],
    order: str = "DESC"
) -> List[Dict[str, Any]]:
    """
    Main entry point for formatting connections to DATA-ID format.

    This function converts graph concepts and connections to a nested JSON format
    that includes both IDs and data for each concept.

    Args:
        linkers: List of connection IDs (linker connections)
        concept_ids: List of concept IDs in the result
        main_composition_ids: List of main composition IDs (root entities)
        reverse: List of connection IDs that should be processed in reverse
        count_infos: List of CountInfo objects for aggregation data
        order: Sort order - "ASC" or "DESC"

    Returns:
        List of formatted composition dictionaries

    Example:
        >>> result = await format_connections_data_id(
        ...     linkers=[1, 2, 3],
        ...     concept_ids=[100, 101, 102],
        ...     main_composition_ids=[100],
        ...     reverse=[],
        ...     count_infos=[],
        ...     order="DESC"
        ... )
        >>> print(result[0]["id"])  # 100
    """
    from ccs.services.search.prefetch import get_connection_data_prefetch
    from ccs.services.search.count_info import get_connection_type_for_count
    from ccs.services.search.ordering import order_connections

    # Step 1: Prefetch all connections and related concepts
    prefetch_connections = await get_connection_data_prefetch(linkers)

    # Step 2: Build count dictionary with enriched type names
    count_dictionary = await get_connection_type_for_count(count_infos)

    # Step 3: Order connections
    prefetch_connections = order_connections(prefetch_connections, order)

    # Step 4: Initialize composition data structures (Pass 1)
    composition_data: Dict[int, Any] = {}
    composition_data = await format_function_data(prefetch_connections, composition_data, reverse)

    # Step 5: Add nested data with IDs (Pass 2)
    composition_data = await format_function_data_for_data(prefetch_connections, composition_data, reverse)

    # Step 6: Stitch together final output (Pass 3)
    output = await format_from_connections_altered_array_external(
        prefetch_connections,
        composition_data,
        main_composition_ids,
        reverse,
        count_dictionary
    )

    return output


async def format_function_data(
    connections: List[Connection],
    composition_data: Dict[int, Any],
    reverse: List[int]
) -> Dict[int, Any]:
    """
    Pass 1: Initialize empty concept structures indexed by ID.

    This pass creates the basic structure for each concept, initializing
    empty dictionaries keyed by the concept's type.

    Args:
        connections: List of prefetched connections
        composition_data: Dictionary to populate (mutated in place)
        reverse: List of connection IDs to process in reverse

    Returns:
        Updated composition_data dictionary
    """
    from ccs.services.get.get_the_concept import GetTheConcept

    for connection in connections:
        reverse_flag = connection.id in reverse

        of_concept = await GetTheConcept(connection.ofTheConceptId)
        to_concept = await GetTheConcept(connection.toTheConceptId)

        if of_concept.id == 0 or to_concept.id == 0:
            continue

        if reverse_flag:
            # Reverse direction: to -> from
            key = to_concept.type.characterValue if to_concept.type else "self"

            if to_concept.id not in composition_data:
                composition_data[to_concept.id] = {}

            if key not in composition_data[to_concept.id]:
                composition_data[to_concept.id][key] = {}

            # Also initialize the of_concept
            if of_concept.id not in composition_data:
                composition_data[of_concept.id] = {}
        else:
            # Forward direction: from -> to
            key = of_concept.type.characterValue if of_concept.type else "self"

            if of_concept.id not in composition_data:
                composition_data[of_concept.id] = {}

            if key not in composition_data[of_concept.id]:
                composition_data[of_concept.id][key] = {}

            # Also initialize the to_concept
            if to_concept.id not in composition_data:
                composition_data[to_concept.id] = {}

    return composition_data


async def format_function_data_for_data(
    connections: List[Connection],
    composition_data: Dict[int, Any],
    reverse: List[int]
) -> Dict[int, Any]:
    """
    Pass 2: Add detailed data properties with ID and data structure.

    This pass populates the nested data structure with actual concept values,
    creating objects with "id" and "data" keys.

    Args:
        connections: List of prefetched connections
        composition_data: Dictionary to update (mutated in place)
        reverse: List of connection IDs to process in reverse

    Returns:
        Updated composition_data dictionary
    """
    from ccs.services.get.get_the_concept import GetTheConcept

    for connection in connections:
        reverse_flag = connection.id in reverse

        of_concept = await GetTheConcept(connection.ofTheConceptId)
        to_concept = await GetTheConcept(connection.toTheConceptId)
        linker_concept = await GetTheConcept(connection.typeId)

        if of_concept.id == 0 or to_concept.id == 0:
            continue

        # Get the linker/connection type name
        data_character = linker_concept.characterValue if linker_concept and linker_concept.id != 0 else ""

        if reverse_flag:
            # Reverse direction: to -> from
            key = to_concept.type.characterValue if to_concept.type else "self"
            my_type = of_concept.type.characterValue if of_concept.type else "value"
            value = of_concept.characterValue

            reverse_character = f"{data_character}_reverse" if data_character else "reverse"

            # Skip _s_ type connections for data (they're composition links)
            if "_s_" in reverse_character:
                continue

            # Build the data object
            data = {
                "id": of_concept.id,
                "data": {my_type: value}
            }

            # Add to composition
            if to_concept.id in composition_data:
                if key not in composition_data[to_concept.id]:
                    composition_data[to_concept.id][key] = {}

                target = composition_data[to_concept.id][key]
                if isinstance(target, dict):
                    target[reverse_character] = data
        else:
            # Forward direction: from -> to
            key = of_concept.type.characterValue if of_concept.type else "self"
            my_type = to_concept.type.characterValue if to_concept.type else "value"
            value = to_concept.characterValue

            # Build the data object
            data = {
                "id": to_concept.id,
                "data": {my_type: value}
            }

            # Handle numeric linkers as arrays, string linkers as objects
            if of_concept.id in composition_data:
                if key not in composition_data[of_concept.id]:
                    composition_data[of_concept.id][key] = {}

                target = composition_data[of_concept.id][key]

                if data_character and not data_character.isdigit():
                    # Skip _s_ type connections for data
                    if "_s_" not in data_character:
                        if isinstance(target, dict):
                            target[data_character] = data
                else:
                    # Numeric or empty - use array
                    if isinstance(target, list):
                        target.append(data)
                    elif isinstance(target, dict):
                        # Convert to array if needed
                        array_key = data_character or "items"
                        if array_key not in target:
                            target[array_key] = []
                        if isinstance(target[array_key], list):
                            target[array_key].append(data)
                        else:
                            target[array_key] = [data]

    return composition_data


async def format_from_connections_altered_array_external(
    connections: List[Connection],
    composition_data: Dict[int, Any],
    main_composition_ids: List[int],
    reverse: List[int],
    count_dictionary: Dict[int, CountInfo]
) -> List[Dict[str, Any]]:
    """
    Pass 3: Stitch compositions together with timestamps and create final output.

    This pass links child compositions to parent compositions and builds
    the final array of main composition objects.

    Args:
        connections: List of prefetched connections
        composition_data: Dictionary of composition data from previous passes
        main_composition_ids: List of IDs for the main/root compositions
        reverse: List of connection IDs to process in reverse
        count_dictionary: Dictionary mapping concept IDs to count info

    Returns:
        List of formatted main composition dictionaries
    """
    from ccs.services.get.get_the_concept import GetTheConcept

    for connection in connections:
        reverse_flag = connection.id in reverse

        of_concept = await GetTheConcept(connection.ofTheConceptId)
        to_concept = await GetTheConcept(connection.toTheConceptId)
        linker_concept = await GetTheConcept(connection.typeId)

        if of_concept.id == 0 or to_concept.id == 0:
            continue

        linker_character = linker_concept.characterValue if linker_concept and linker_concept.id != 0 else ""

        # Only process _s_ (composition link) connections in this pass
        if "_s_" not in linker_character:
            continue

        # Get timestamp
        created_on = None
        if connection.entryTimeStamp:
            created_on = connection.entryTimeStamp.isoformat()

        if reverse_flag:
            # Reverse direction: to -> from
            if to_concept.id in composition_data:
                key = to_concept.type.characterValue if to_concept.type else "self"
                reverse_character = f"{linker_character}_reverse"

                # Build linked data with nested composition
                linked_data = {
                    "id": of_concept.id,
                    "data": composition_data.get(of_concept.id, {}),
                }
                if created_on:
                    linked_data["created_on"] = created_on

                # Add count info if available
                if of_concept.id in count_dictionary:
                    linked_data["count"] = count_dictionary[of_concept.id].count

                # Add to parent composition
                target = composition_data[to_concept.id]
                if key not in target:
                    target[key] = {}

                if isinstance(target[key], dict):
                    if reverse_character in target[key]:
                        # Already exists - convert to array
                        existing = target[key][reverse_character]
                        if isinstance(existing, list):
                            existing.append(linked_data)
                        else:
                            target[key][reverse_character] = [existing, linked_data]
                    else:
                        target[key][reverse_character] = [linked_data]
        else:
            # Forward direction: from -> to
            if of_concept.id in composition_data:
                key = of_concept.type.characterValue if of_concept.type else "self"

                # Build linked data with nested composition
                linked_data = {
                    "id": to_concept.id,
                    "data": composition_data.get(to_concept.id, {}),
                }
                if created_on:
                    linked_data["created_on"] = created_on

                # Add count info if available
                if to_concept.id in count_dictionary:
                    linked_data["count"] = count_dictionary[to_concept.id].count

                # Add to parent composition
                target = composition_data[of_concept.id]
                if key not in target:
                    target[key] = {}

                if isinstance(target[key], dict):
                    if linker_character in target[key]:
                        # Already exists - convert to array
                        existing = target[key][linker_character]
                        if isinstance(existing, list):
                            existing.append(linked_data)
                        else:
                            target[key][linker_character] = [existing, linked_data]
                    else:
                        target[key][linker_character] = [linked_data]

    # Build final output for main compositions
    main_data: List[Dict[str, Any]] = []

    for main_id in main_composition_ids:
        main_item = {
            "id": main_id,
            "data": composition_data.get(main_id, {}),
        }

        # Add count info if available
        if main_id in count_dictionary:
            main_item["count"] = count_dictionary[main_id].count

        main_data.append(main_item)

    return main_data
