from dataclasses import dataclass
import hashlib
from pathlib import Path
import re
import tomllib

from platformdirs import PlatformDirs

APP_NAME = "rimpack"
APP_AUTHOR = "rimpack"
CONFIG_FILE_NAME = "config.toml"


class RimpackConfigError(ValueError):
    """Raised when the user config file is invalid."""


@dataclass(frozen=True, slots=True)
class RimpackConfig:
    rimworld_folder: Path | None = None
    steam_workshop_folder: Path | None = None


def get_config_directory() -> Path:
    return PlatformDirs(appname=APP_NAME, appauthor=APP_AUTHOR).user_config_path


def get_config_file_path() -> Path:
    return get_config_directory() / CONFIG_FILE_NAME


def get_pack_state_directory(root_directory: Path) -> Path:
    resolved_root = root_directory.resolve()
    digest = hashlib.sha256(str(resolved_root).encode("utf-8")).hexdigest()[:12]
    normalized_name = re.sub(r"[^A-Za-z0-9]+", "-", resolved_root.name).strip("-")
    pack_name = normalized_name or "modpack"
    return get_config_directory() / "packs" / f"{pack_name}-{digest}"


def get_pack_runtime_root(root_directory: Path) -> Path:
    return get_pack_state_directory(root_directory) / "runtime" / "RimWorld"


def get_pack_save_data_root(root_directory: Path) -> Path:
    return get_pack_state_directory(root_directory) / "save-data"


def ensure_config_directory_exists() -> Path:
    config_directory = get_config_directory()
    config_directory.mkdir(parents=True, exist_ok=True)
    return config_directory


def load_config(path: Path | None = None) -> RimpackConfig:
    config_path = path or get_config_file_path()
    try:
        document = tomllib.loads(config_path.read_text(encoding="utf-8"))
    except FileNotFoundError as exc:
        raise RimpackConfigError(f"Config file does not exist: {config_path}") from exc

    rimworld_folder = _optional_path_value(document, "rimworld_folder", config_path)
    steam_workshop_folder = _optional_path_value(
        document,
        "steam_workshop_folder",
        config_path,
    )
    return RimpackConfig(
        rimworld_folder=rimworld_folder,
        steam_workshop_folder=steam_workshop_folder,
    )


def load_config_if_exists(path: Path | None = None) -> RimpackConfig | None:
    config_path = path or get_config_file_path()
    if not config_path.is_file():
        return None
    return load_config(config_path)


def write_config(config: RimpackConfig, path: Path | None = None) -> Path:
    config_path = path or get_config_file_path()
    config_path.parent.mkdir(parents=True, exist_ok=True)
    _ = config_path.write_text(render_config(config), encoding="utf-8", newline="\n")
    return config_path


def render_config(config: RimpackConfig) -> str:
    lines: list[str] = []
    if config.rimworld_folder is not None:
        lines.append(f'rimworld_folder = "{config.rimworld_folder.as_posix()}"')
    if config.steam_workshop_folder is not None:
        lines.append(
            f'steam_workshop_folder = "{config.steam_workshop_folder.as_posix()}"'
        )
    if not lines:
        return ""
    return "\n".join(lines) + "\n"


def _optional_path_value(
    document: dict[str, object],
    key: str,
    config_path: Path,
) -> Path | None:
    value = document.get(key)
    if value is None:
        return None
    if not isinstance(value, str):
        raise RimpackConfigError(
            f'Config key "{key}" must be a string path in {config_path}'
        )
    return Path(value)
