from __future__ import annotations

import json
import logging
import os
import secrets
from collections.abc import Callable
from dataclasses import dataclass, field, asdict
from pathlib import Path

logger = logging.getLogger(__name__)

DEFAULT_CONFIG_DIR = Path.home() / ".sitedrop"
DEFAULT_CONFIG_PATH = Path(
    os.environ.get("SITEDROP_CONFIG", DEFAULT_CONFIG_DIR / "server.json")
)
DEFAULT_SITES_DIR = DEFAULT_CONFIG_DIR / "sites"

CURRENT_CONFIG_VERSION = 1
_MIGRATIONS: dict[int, Callable[[dict], dict]] = {}


def _migrate_config(data: dict) -> dict:
    version = data.get("config_version", 0)
    while version < CURRENT_CONFIG_VERSION:
        if version in _MIGRATIONS:
            data = _MIGRATIONS[version](data)
        version += 1
        data["config_version"] = version
    return data


@dataclass
class ServerConfig:
    password_hash: str = ""
    jwt_secret: str = field(default_factory=lambda: secrets.token_hex(32))
    host: str = "127.0.0.1"
    port: int = 8000
    sites_dir: str = str(DEFAULT_SITES_DIR)
    ssl_certfile: str = ""
    ssl_keyfile: str = ""
    config_version: int = CURRENT_CONFIG_VERSION

    def save(self, path: Path = DEFAULT_CONFIG_PATH) -> None:
        try:
            path.parent.mkdir(parents=True, exist_ok=True)
            path.write_text(json.dumps(asdict(self), indent=2))
            os.chmod(path, 0o600)
        except OSError as e:
            raise OSError(f"Failed to save config to {path}: {e}") from e

    @classmethod
    def load(cls, path: Path = DEFAULT_CONFIG_PATH) -> ServerConfig:
        if not path.exists():
            return cls()
        try:
            data = json.loads(path.read_text())
        except (json.JSONDecodeError, OSError) as e:
            logger.warning("Failed to load server config from %s: %s", path, e)
            return cls()
        data = _migrate_config(data)
        if data.get("config_version", 0) > CURRENT_CONFIG_VERSION:
            logger.warning(
                "Config version %s is newer than supported version %s",
                data["config_version"],
                CURRENT_CONFIG_VERSION,
            )
        return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
