from __future__ import annotations

import copy
import inspect
from typing import Any, Dict, Optional


async def build_data_id_normalization_map(
    *,
    node_type: str,
    local_collection,
    remote_collection,
    binding_manager,
) -> Dict[str, str]:
    mapping: Dict[str, str] = {}
    records_or_awaitable = binding_manager.get_all_records(node_type)
    records = await records_or_awaitable if inspect.isawaitable(records_or_awaitable) else records_or_awaitable
    for record in records:
        if not record.local_id or not record.remote_id:
            continue
        local_node = local_collection.get_by_node_id(record.local_id)
        remote_node = remote_collection.get_by_node_id(record.remote_id)
        if local_node is None or remote_node is None:
            continue
        local_data_id = local_node.data_id
        remote_data_id = remote_node.data_id
        if not local_data_id:
            continue
        mapping[local_data_id] = local_data_id
        if remote_data_id:
            mapping[remote_data_id] = local_data_id
    return mapping


def is_id_like_field(field_name: Optional[str]) -> bool:
    if not field_name:
        return False
    lowered = field_name.lower()
    return lowered.endswith("_id") or lowered.endswith("_ids")


def normalize_compare_value(
    field_name: Optional[str],
    value: Any,
    data_id_map: Dict[str, str],
) -> Any:
    if isinstance(value, dict):
        return {
            key: normalize_compare_value(key, nested, data_id_map)
            for key, nested in value.items()
        }
    if isinstance(value, list):
        return [normalize_compare_value(field_name, item, data_id_map) for item in value]

    if value == "":
        value = None

    if is_id_like_field(field_name) and isinstance(value, str):
        return data_id_map.get(value, value)
    return value


def normalized_data_for_compare(
    data: Optional[Dict[str, Any]],
    data_id_map: Optional[Dict[str, str]] = None,
    ignore_fields: Optional[set[str]] = None,
) -> Dict[str, Any]:
    payload = copy.deepcopy(data or {})
    payload.pop("id", None)
    payload.pop("_id", None)
    ignored = ignore_fields or set()
    mapping = data_id_map or {}
    return {
        key: normalize_compare_value(key, value, mapping)
        for key, value in payload.items()
        if key not in ignored
    }


def collect_payload_diffs(
    source_payload: Dict[str, Any],
    target_payload: Dict[str, Any],
    *,
    max_items: int = 6,
    ignore_fields: Optional[set[str]] = None,
) -> Dict[str, Dict[str, Any]]:
    out: Dict[str, Dict[str, Any]] = {}
    ignored = ignore_fields or set()
    keys = list(dict.fromkeys(list(source_payload.keys()) + list(target_payload.keys())))
    for key in keys:
        if key in ignored:
            continue
        source_value = source_payload.get(key)
        target_value = target_payload.get(key)
        if source_value == target_value:
            continue
        out[key] = {"source": source_value, "target": target_value}
        if len(out) >= max_items:
            break
    return out
