"""CRUD-style operations on the in-memory UMC data structure (categories, maps, options)."""

from typing import Any

from core.domain.validator import (
    validate_category,
    validate_category_option,
    validate_map,
    validate_map_option,
)
from core.exceptions import CategoryAlreadyExists, CategoryNotFound, MapAlreadyExists, MapNotFound

UMCData = dict[str, dict[str, Any]]


# ==========================================================================
# GESTION DES CATÉGORIES
# ==========================================================================


def category_exists(umc_data: UMCData, category_name: str) -> bool:
    """Check whether a category exists in the UMC data."""
    return category_name in umc_data


def get_categories(umc_data: UMCData) -> list[str]:
    """Returns a sorted list of all categories."""
    return sorted(umc_data.keys())


def add_category(
    umc_data: UMCData, category_name: str, options: dict[str, Any] | None = None
) -> None:
    """Adds a new empty category with its options."""
    validate_category(category_name, options)
    if category_exists(umc_data, category_name):
        msg = f"La catégorie '{category_name}' existe déjà."
        raise CategoryAlreadyExists(msg)
    umc_data[category_name] = {"options": options or {}, "maps": {}}


def remove_category(umc_data: UMCData, category_name: str) -> None:
    """Deletes a category and all the maps it contains."""
    if not category_exists(umc_data, category_name):
        msg = f"Catégorie introuvable : {category_name}"
        raise CategoryNotFound(msg)
    del umc_data[category_name]


# ==========================================================================
# GESTION DES MAPS
# ==========================================================================


def map_exists(umc_data: UMCData, category_name: str, map_name: str) -> bool:
    """Checks whether a specific map exists in a given category."""
    if not category_exists(umc_data, category_name):
        return False
    return map_name in umc_data[category_name]["maps"]


def map_exists_raise_error(umc_data: UMCData, category_name: str, map_name: str) -> None:
    """Throws an exception if a map already exists in the category."""
    if map_name in umc_data[category_name]["maps"]:
        msg = f"La map '{map_name}' existe déjà dans '{category_name}'."
        raise MapAlreadyExists(msg)


def map_exists_anywhere(umc_data: UMCData, map_name: str) -> bool:
    """Scan the entire file to see if the map is linked anywhere."""
    return any(map_name in category["maps"] for category in umc_data.values())


def find_map_categories(umc_data: UMCData, map_name: str) -> list[str]:
    """Find all categories that contain a specific map."""
    categories = []
    for category_name, category_data in umc_data.items():
        if map_name in category_data["maps"]:
            categories.append(category_name)
    return categories


def get_maps(umc_data: UMCData, category_name: str) -> list[str]:
    """Returns a sorted list of all maps in a category."""
    if not category_exists(umc_data, category_name):
        msg = f"Catégorie introuvable : {category_name}"
        raise CategoryNotFound(msg)
    return sorted(umc_data[category_name]["maps"].keys())


def add_map_to_category(
    umc_data: UMCData, category_name: str, map_name: str, options: dict[str, Any] | None = None
) -> None:
    """Includes a map and its configuration options in a category.

    Raises:
        CategoryNotFound: if `category_name` does not exist.
        MapAlreadyExists: if `map_name` is already present in the category.
        ValidationError: if `map_name` or `options` are invalid.
    """
    if not category_exists(umc_data, category_name):
        msg = f"Category not found: {category_name}"
        raise CategoryNotFound(msg)
    map_exists_raise_error(umc_data, category_name, map_name)
    validate_map(map_name, options)
    umc_data[category_name]["maps"][map_name] = options or {}


def remove_map(umc_data: UMCData, category_name: str, map_name: str) -> None:
    """Removes a map from a specific category."""
    if not category_exists(umc_data, category_name):
        msg = f"Catégorie introuvable : {category_name}"
        raise CategoryNotFound(msg)
    if not map_exists(umc_data, category_name, map_name):
        msg = f"Map introuvable : {map_name}"
        raise MapNotFound(msg)
    del umc_data[category_name]["maps"][map_name]


# ==========================================================================
# MANAGEMENT DES OPTIONS INDIVIDUELLES (CVARs / Clés-Valeurs)
# ==========================================================================


def get_category_options(umc_data: UMCData, category_name: str) -> dict[str, Any]:
    """Retrieves the options dictionary for a category."""
    if not category_exists(umc_data, category_name):
        msg = f"Catégorie introuvable : {category_name}"
        raise CategoryNotFound(msg)
    return umc_data[category_name]["options"]


def set_category_option(umc_data: UMCData, category_name: str, key: str, value: Any) -> None:
    """Sets or changes a configuration option for a category."""
    if not category_exists(umc_data, category_name):
        msg = f"Catégorie introuvable : {category_name}"
        raise CategoryNotFound(msg)
    if not validate_category_option(key, value):
        msg = f"Option catégorie invalide : {key}"
        raise ValueError(msg)
    umc_data[category_name]["options"][key] = value


def remove_category_option(umc_data: UMCData, category_name: str, key: str) -> None:
    """Removes an option from a category without error, if it exists."""
    if not category_exists(umc_data, category_name):
        msg = f"Catégorie introuvable : {category_name}"
        raise CategoryNotFound(msg)
    umc_data[category_name]["options"].pop(key, None)


def get_map_options(umc_data: UMCData, category_name: str, map_name: str) -> dict[str, Any]:
    """Retrieves the configuration options assigned to a map."""
    if not map_exists(umc_data, category_name, map_name):
        msg = f"Map introuvable : {map_name}"
        raise MapNotFound(msg)
    return umc_data[category_name]["maps"][map_name]


def set_map_option(
    umc_data: UMCData, category_name: str, map_name: str, key: str, value: Any
) -> None:
    """Modifies or creates an option for a map within a category."""
    if not map_exists(umc_data, category_name, map_name):
        msg = f"Map introuvable : {map_name}"
        raise MapNotFound(msg)
    if not validate_map_option(key, value):
        msg = f"Option map invalide : {key}"
        raise ValueError(msg)
    umc_data[category_name]["maps"][map_name][key] = value


def remove_map_option(umc_data: UMCData, category_name: str, map_name: str, key: str) -> None:
    """Removes an option from a map without an error if it did not exist."""
    if not map_exists(umc_data, category_name, map_name):
        msg = f"Map introuvable : {map_name}"
        raise MapNotFound(msg)
    umc_data[category_name]["maps"][map_name].pop(key, None)


def get_category_count(umc_data: UMCData) -> int:
    """Returns the total number of categories."""
    return len(umc_data)
