from __future__ import annotations

from dataclasses import dataclass, field
from pathlib import Path
import os
import tomllib

DEFAULT_CONFIG_FILENAME = "obsidian-sync.toml"
DEFAULT_EXCLUDES = [
    ".obsidian-sync-state",
    ".obsidian-sync-backups",
    ".DS_Store",
    "Thumbs.db",
]


@dataclass(slots=True)
class PathsConfig:
    source_vault: Path
    synology_vault: Path
    windows_synology_vault: str = ""


@dataclass(slots=True)
class SyncConfig:
    exclude: list[str] = field(default_factory=lambda: list(DEFAULT_EXCLUDES))
    debounce_seconds: float = 1.0
    poll_interval_seconds: float = 30.0
    backup_dir: str = ".obsidian-sync-backups"


@dataclass(slots=True)
class AppConfig:
    paths: PathsConfig
    sync: SyncConfig
    config_path: Path
    state_db_path: Path

    @property
    def state_dir(self) -> Path:
        return self.state_db_path.parent

    @classmethod
    def load(cls, config_path: Path | str = DEFAULT_CONFIG_FILENAME) -> "AppConfig":
        path = Path(config_path).expanduser().resolve()
        with path.open("rb") as handle:
            raw = tomllib.load(handle)

        paths = raw.get("paths", {})
        sync = raw.get("sync", {})

        source_vault = _expand_to_abs(paths["source_vault"])
        synology_vault = _expand_to_abs(paths["synology_vault"])
        windows_synology_vault = str(paths.get("windows_synology_vault", ""))

        sync_cfg = SyncConfig(
            exclude=list(sync.get("exclude", DEFAULT_EXCLUDES)),
            debounce_seconds=float(sync.get("debounce_seconds", 1.0)),
            poll_interval_seconds=float(sync.get("poll_interval_seconds", 30.0)),
            backup_dir=str(sync.get("backup_dir", ".obsidian-sync-backups")),
        )

        state_db_raw = raw.get("state", {}).get("db_path")
        if state_db_raw:
            state_db = _expand_to_abs(str(state_db_raw))
        else:
            state_db = path.parent / ".obsidian-sync-state" / "state.db"

        return cls(
            paths=PathsConfig(
                source_vault=source_vault,
                synology_vault=synology_vault,
                windows_synology_vault=windows_synology_vault,
            ),
            sync=sync_cfg,
            config_path=path,
            state_db_path=state_db,
        )

    def ensure_directories(self) -> None:
        self.paths.source_vault.mkdir(parents=True, exist_ok=True)
        self.paths.synology_vault.mkdir(parents=True, exist_ok=True)
        self.state_dir.mkdir(parents=True, exist_ok=True)


def write_default_config(
    config_path: Path | str,
    *,
    source_vault: Path | str,
    synology_vault: Path | str,
    windows_synology_vault: str,
    force: bool = False,
) -> Path:
    path = Path(config_path).expanduser().resolve()
    if path.exists() and not force:
        raise FileExistsError(f"Config already exists at {path}")

    source_abs = _expand_to_abs(str(source_vault))
    target_abs = _expand_to_abs(str(synology_vault))
    state_db = path.parent / ".obsidian-sync-state" / "state.db"

    content = _build_toml(
        source_vault=source_abs,
        synology_vault=target_abs,
        windows_synology_vault=windows_synology_vault,
        state_db_path=state_db,
    )

    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(content, encoding="utf-8")
    return path


def _build_toml(
    *,
    source_vault: Path,
    synology_vault: Path,
    windows_synology_vault: str,
    state_db_path: Path,
) -> str:
    quoted_excludes = ", ".join(_toml_string(x) for x in DEFAULT_EXCLUDES)
    lines = [
        "[paths]",
        f"source_vault = {_toml_string(str(source_vault))}",
        f"synology_vault = {_toml_string(str(synology_vault))}",
        f"windows_synology_vault = {_toml_string(windows_synology_vault)}",
        "",
        "[sync]",
        f"exclude = [{quoted_excludes}]",
        "debounce_seconds = 1.0",
        "poll_interval_seconds = 30.0",
        "backup_dir = \".obsidian-sync-backups\"",
        "",
        "[state]",
        f"db_path = {_toml_string(str(state_db_path))}",
        "",
    ]
    return "\n".join(lines)


def _toml_string(value: str) -> str:
    escaped = value.replace("\\", "\\\\").replace('"', '\\"')
    return f'"{escaped}"'


def _expand_to_abs(value: str) -> Path:
    expanded = os.path.expandvars(os.path.expanduser(value))
    return Path(expanded).resolve()
