import collections.abc
import json
import pathlib

import numpy as np


def update_boundaries(boundary, vertex_offset):
    """
    Recursively updates vertex indices in a boundary by adding a vertex offset.

    Parameters:
        boundary (list): A list of vertex indices or nested lists of vertex indices.
        vertex_offset (int): The offset to add to each vertex index.

    Returns:
        list: Updated boundary with vertex indices incremented by the offset.
    """

    if isinstance(boundary[0], collections.abc.Sequence):
        updated_boundaries = []
        for subboundary in boundary:
            updated_boundaries.append(update_boundaries(subboundary, vertex_offset))
        return updated_boundaries
    else:
        return [vertex + vertex_offset for vertex in boundary]


def update_geometry_indices(geometry, vertex_offset):
    """
    Updates the vertex indices of geometries in a CityJSON object.

    Parameters:
        geometry (list[dict])
        vertex_offset (int):
    Returns:
        None
    """
    for geom in geometry:
        if "boundaries" in geom:
            for boundary in geom["boundaries"]:
                updated_boundary = update_boundaries(boundary, vertex_offset)
                boundary.clear()
                boundary.extend(updated_boundary)


def check_crs(files):
    crs_system = []
    for file in files:
        with open(file) as f:
            data = json.load(f)
            crs_system.append(data["metadata"]["referenceSystem"])
    same_crs = set(crs_system)
    if len(same_crs) != 1:
        raise ValueError(f"Not the same crs {list(same_crs)}")


def merged_cityjson(files: list[pathlib.Path], merged_file_path: pathlib.Path) -> None:
    """
    Merges multiple CityJSON files into a single CityJSON file.

    This function handles updating vertex indices, geometry boundaries,
    and metadata such as geographical extents. It also appends a unique
    suffix to CityObject IDs to avoid key collisions.

    Parameters:
        files (list[pathlib.Path]): List of paths to input CityJSON files.
        merged_file_path (pathlib.Path): Path where the merged CityJSON will be saved.

    Returns:
        None
    """
    # Dictionnary template that be re-compute at the end
    check_crs(files)
    merged_data: dict = {
        "type": "CityJSON",
        "version": "1.1",
        "transform": {
            "scale": [1, 1, 1],
            "translate": [0, 0, 0],
        },
        "metadata": {
            "geographicalExtent": [],
            "referenceSystem": "EPSG::2154",
        },
        "CityObjects": {},
        "vertices": [],
    }
    min_extent = [float("inf"), float("inf"), float("inf")]
    max_extent = [float("-inf"), float("-inf"), float("-inf")]

    vertex_offset = 0  # Offset to update geometry indices

    for file in files:
        with open(file) as f:
            data = json.load(f)
            cityobjects = data["CityObjects"]
            vertices = data["vertices"]

            scale = np.array(data.get("transform", {}).get("scale", [1.0, 1.0, 1.0]))
            translate = np.array(
                data.get("transform", {}).get("translate", [0.0, 0.0, 0.0])
            )

            # Update CityObjects with new vertex indices
            for cityobject_id, cityobject in cityobjects.items():
                new_cityobject_id = (
                    f"{cityobject_id}_{file.stem}"  # Update CityObject key
                )
                updated_cityobject = cityobject.copy()
                update_geometry_indices(updated_cityobject["geometry"], vertex_offset)
                if updated_cityobject.get("parents"):
                    updated_cityobject["parents"] = [
                        f"{parent}_{file}" for parent in updated_cityobject["parents"]
                    ]
                if updated_cityobject.get("children"):
                    updated_cityobject["children"] = [
                        f"{child}_{file}" for child in updated_cityobject["children"]
                    ]

                merged_data["CityObjects"][new_cityobject_id] = updated_cityobject

            transformed_vertices = (vertices * scale) + translate
            merged_data["vertices"].extend(transformed_vertices.tolist())
            vertex_offset += len(vertices)

            file_extent = data["metadata"]["geographicalExtent"]
            if file_extent:
                min_extent = [min(min_extent[i], file_extent[i]) for i in range(3)]
                max_extent = [max(max_extent[i], file_extent[i + 3]) for i in range(3)]

    merged_data["metadata"]["referenceSystem"] = data["metadata"]["referenceSystem"]
    merged_data["metadata"]["geographicalExtent"] = min_extent + max_extent

    with open(merged_file_path, "w") as f:
        json.dump(merged_data, f)
