from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
import logging
import threading
import time

from .config import AppConfig
from .lock import InstanceLock, InstanceLockError
from .sync_engine import SyncEngine
from .watcher import run_watch_loop_until

logger = logging.getLogger(__name__)
LOCK_FILENAME = "obsidian-sync.lock"


@dataclass(slots=True)
class _WorkerResult:
    config_path: Path
    error: str | None = None


def run_multi_watch(
    config_paths: list[str],
    *,
    dry_run: bool = False,
    no_lock: bool = False,
) -> int:
    unique_paths: list[Path] = []
    seen: set[Path] = set()
    for raw in config_paths:
        path = Path(raw).expanduser().resolve()
        if path in seen:
            continue
        seen.add(path)
        unique_paths.append(path)

    if not unique_paths:
        raise ValueError("At least one config path is required.")

    stop_event = threading.Event()
    errors: list[_WorkerResult] = []
    error_lock = threading.Lock()
    threads: list[threading.Thread] = []

    def worker(config_path: Path) -> None:
        try:
            config = AppConfig.load(config_path)
            config.ensure_directories()

            with InstanceLock(config.state_dir / LOCK_FILENAME, enabled=not no_lock):
                engine = SyncEngine(config)
                try:
                    initial = engine.sync_once(dry_run=dry_run)
                    logger.info("[%s] Initial sync complete: %s", config_path, initial.to_dict())
                    run_watch_loop_until(engine, dry_run=dry_run, stop_event=stop_event)
                finally:
                    engine.close()
        except (InstanceLockError, RuntimeError, ValueError, FileNotFoundError) as exc:
            with error_lock:
                errors.append(_WorkerResult(config_path=config_path, error=str(exc)))
            stop_event.set()

    for path in unique_paths:
        thread = threading.Thread(target=worker, name=f"watch-{path.name}", args=(path,), daemon=True)
        thread.start()
        threads.append(thread)

    try:
        while any(thread.is_alive() for thread in threads):
            if stop_event.is_set():
                break
            time.sleep(0.25)
    except KeyboardInterrupt:
        logger.info("Stopping all watch workers by keyboard interrupt.")
        stop_event.set()

    for thread in threads:
        thread.join(timeout=5)

    if errors:
        for item in errors:
            logger.error("[%s] worker failed: %s", item.config_path, item.error)
        return 2

    return 0
