from __future__ import annotations

import json
from pathlib import Path

import jsonschema


class Configuration:


    def get_conf_repo_path(self) -> Path:
        """
        Retrieves the configuration repository path.

        Returns:
            Path: The path to the configuration repository as a Path object.
        """
        return self.get_existing_path("conf_repository_path")

    def get_crema_id(self) -> str:
        """
        Retrieves the Crema ID from the configuration.

        Returns:
            str: The Crema ID.
        """
        return self.get_not_empty("crema_id")

    def get_prime_mapping_path(self) -> Path:
        """
        Retrieves the path to the prime mapping file.

        Returns:
            Path: The path to the prime mapping file as a Path object.
        """
        return self.get_existing_path(["runner_configuration","prime_mapping_file"])

    def get_segmentation_file(self) -> Path:
        return self.get_existing_path("segmentation_file")

    def has_segmentation_file(self) -> bool:
        return self.has_attribute("segmentation_file")

    def get_runner_configuration(self):
        return self.get_attribute("runner_configuration")

    def has_attribute(self, route: str | list[str]) -> bool:
        try:
            self.get_attribute(route)
        except RouteNotFoundError:
            return False
        return True


    def get_attribute(self, route: str | list[str]):
        """
        Retrieves a value from the configuration.

        Args:
            route (str | list[str]): A key or a list of nested keys.

        Returns:
            Any: The value found at the given key(s).

        Raises:
            RouteNotFoundError: If the key or nested path does not exist.
        """
        conf = self.__conf__

        if isinstance(route, str):
            if route not in conf:
                raise RouteNotFoundError(route)
            return conf[route]

        if isinstance(route, list):
            for key in route:
                if not isinstance(conf, dict) or key not in conf:
                    raise RouteNotFoundError(route)
                conf = conf[key]
            return conf

        raise TypeError(f"route must be str or list of str, got {type(route).__name__}")


    def get_existing_path(self, route: str):
        route_path = Path(self.get_not_empty(route))
        if not route_path.exists():
            raise NotExistingPathError(route)
        return route_path

    def get_not_empty(self, route: str):
        value = self.get_attribute(route)
        if value is None or not value.strip():
            raise EmptyMandatoryFieldError(route)
        return value

    @staticmethod
    def from_json_file(path: Path):
        """
        Loads and returns a Configuration object from a JSON file.

        This static method reads a JSON configuration file from the given path,
        parses it into a SimpleNamespace object, and assigns it to the internal
        configuration attribute of a new Configuration instance.

        Args:
            path (Path): The path to the JSON configuration file.

        Returns:
            Configuration: A Configuration object populated with the data from the JSON file.
        """
        return Configuration.from_json(path.open("r").read())

    @staticmethod
    def from_json(json_object: str):
        """
        Loads and returns a Configuration object from a JSON file.

        This static method reads a JSON configuration file from the given path,
        parses it into a SimpleNamespace object, and assigns it to the internal
        configuration attribute of a new Configuration instance.

        Args:
            path (Path): The path to the JSON configuration file.

        Returns:
            Configuration: A Configuration object populated with the data from the JSON file.
        """

        if not json_object or not json_object.strip():
            raise SchemaValidationEmptyFileError

        conf = Configuration()
        schema_path = Path(__file__).parent / "data" / "configuration_schema.json"

        try:
            jsonschema.validate(json.loads(json_object), json.loads(schema_path.open("r").read()))
        except jsonschema.ValidationError as e:
            raise SchemaValidationError(e) from e

        conf.__conf__ = json.loads(json_object)


        return conf



class ConfigurationError(Exception):
    PREFIX = "[Configuration] "

    def __init__(self, message=""):
        full_message = f"{self.PREFIX}{message}"
        super().__init__(full_message)

class RouteNotFoundError(ConfigurationError):
    """Raised when a configuration route/key does not exist."""

    def __init__(self, route: str | list[str]):
        route_str = route if isinstance(route, str) else " -> ".join(route)
        super().__init__(f"Route not found: {route_str}")

class MissingMandatoryFieldError(ConfigurationError):
    def __init__(self, field_name: str):
        super().__init__(f"Missing mandatory field: {field_name}")


class EmptyMandatoryFieldError(ConfigurationError):
    def __init__(self, field_name: str):
        super().__init__(f"Empty mandatory field: {field_name}")

class NotExistingPathError(ConfigurationError):
    def __init__(self, field_name: str):
        super().__init__(f"Non-exisisting path for field field: {field_name}")


class SchemaValidationError(ConfigurationError):
    def __init__(self, error):
        super().__init__(f"Error at {'/'.join([ str(i) for i in error.path])}: {error.message}")

class SchemaValidationEmptyFileError(ConfigurationError):
    def __init__(self):
        super().__init__("No JSON content")
