import json
from pathlib import Path
from typing import Any, Callable, NotRequired, TypedDict, cast

from tools.utils import get_executable_dir

class ConfigData(TypedDict):
    discord_user_id: NotRequired[str]
    volume: NotRequired[int]

Validator = Callable[[Any], bool]
Normalizer = Callable[[Any], Any]

class ConfigField(TypedDict):
    default: Any
    validator: Validator
    normalizer: Normalizer

CONFIG_PATH = get_executable_dir() / "config.json"

DISCORD_USER_KEY = "discord_user_id"
VOLUME_KEY = "volume"

CONFIG_SCHEMA: dict[str, ConfigField] = {
    DISCORD_USER_KEY: {
        "default": "",
        "validator": lambda value: isinstance(value, str),
        "normalizer": lambda value: str(value).strip(),
    },
    VOLUME_KEY: {
        "default": 30,
        "validator": lambda value: isinstance(value, int) and 0 <= value <= 100,
        "normalizer": lambda value: max(0, min(int(value), 100)),
    },
}

class ConfigManager:
    def __init__(self, path: Path | None = None) -> None:
        self.path = path or CONFIG_PATH
        self._data: ConfigData = self._load()
        self._dirty = False

    # Lecture du fichier de configuration
    def _load(self) -> ConfigData:
        if not self.path.exists():
            return {}

        try:
            with self.path.open("r", encoding="utf-8") as file:
                data = json.load(file)
        except (json.JSONDecodeError, OSError):
            return {}

        if not isinstance(data, dict):
            return {}

        return cast(ConfigData, data)

    # Sauvegarde du fichier de configuration
    def save(self) -> None:
        if not self._dirty:
            return

        self.path.parent.mkdir(parents=True, exist_ok=True)

        with self.path.open("w", encoding="utf-8") as file:
            json.dump(self._data, file, indent=4, ensure_ascii=False)

        self._dirty = False

    def _get_field(self, key: str) -> ConfigField:
        if key not in CONFIG_SCHEMA:
            raise KeyError(f"Unknown config key: {key}")
        return CONFIG_SCHEMA[key]

    def get(self, key: str) -> Any:
        field = self._get_field(key)
        value = self._data.get(key, field["default"])

        if not field["validator"](value):
            return field["default"]

        return value

    def set(self, key: str, value: Any) -> None:
        field = self._get_field(key)

        normalized = field["normalizer"](value)

        if not field["validator"](normalized):
            raise ValueError(f"Invalid value for {key}")

        if self._data.get(key) == normalized:
            return

        self._data[key] = normalized
        self._dirty = True

    def reset_all(self) -> None:
        defaults: ConfigData = cast(
            ConfigData,
            {key: field["default"] for key, field in CONFIG_SCHEMA.items()},
        )
        self.save(defaults)

    def get_all(self) -> ConfigData:
        return cast(
            ConfigData,
            {key: self.get(key) for key in CONFIG_SCHEMA},
        )

    # ---------------------------------------------------------------------------
    #  SETTERS MÉTIER
    # ---------------------------------------------------------------------------

    # Set Discord ID
    def set_discord_user(self, user_id: str) -> None:
        self.set(DISCORD_USER_KEY, user_id)

    # Set volume
    def set_volume(self, volume: int) -> None:
        self.set(VOLUME_KEY, volume)

    # ---------------------------------------------------------------------------
    #  GETTERS MÉTIER
    # ---------------------------------------------------------------------------

    # Get discord ID
    def get_default(self, key: str):
        return CONFIG_SCHEMA[key]["default"]

    # Get volume value
    def get_discord_user(self) -> str:
        return cast(str, self.get(DISCORD_USER_KEY))

    def get_volume(self) -> int:
        return cast(int, self.get(VOLUME_KEY))
