"""Watch command for file syncing."""

import json
import sys
import time
from pathlib import Path
from typing import Annotated, Optional

import typer

from nogic.config import Config, is_dev_mode, get_api_url
from nogic.ignore import build_ignore_matcher
from nogic.watcher import FileMonitor, SyncService
from nogic import ui


def _emit_json(event: str, **kwargs):
    """Emit a single NDJSON line to stdout."""
    payload = {"event": event, "timestamp": int(time.time()), **kwargs}
    sys.stdout.write(json.dumps(payload) + "\n")
    sys.stdout.flush()


def watch(
    directory: Annotated[Path, typer.Argument(help="Path to the directory to watch.")] = Path("."),
    ignore: Annotated[Optional[list[str]], typer.Option("--ignore", help="Patterns to ignore.")] = None,
    format: Annotated[Optional[str], typer.Option("--format", help="Output format: text or json.")] = None,
):
    """Watch a directory for file changes and sync to backend."""
    directory = directory.resolve()
    nogic_dir = directory / ".nogic"
    ignore = ignore or []
    json_mode = format == "json"

    if not nogic_dir.exists():
        if json_mode:
            _emit_json("error", message="Not a Nogic project. Run `nogic init` to initialize.")
        else:
            ui.error("Not a Nogic project.")
            ui.dim("Run `nogic init` to initialize your project.")
        raise typer.Exit(1)

    config = Config.load(directory)

    if not config.api_key:
        if json_mode:
            _emit_json("error", message="Not logged in. Run `nogic login` to authenticate.")
        else:
            ui.error("Not logged in.")
            ui.dim("Run `nogic login` to authenticate.")
        raise typer.Exit(1)

    if not config.project_id:
        if json_mode:
            _emit_json("error", message="No project configured. Run `nogic init` to initialize.")
        else:
            ui.error("No project configured.")
            ui.dim("Run `nogic init` to initialize your project.")
        raise typer.Exit(1)

    if not json_mode:
        if is_dev_mode():
            ui.dev_banner(get_api_url())
        ui.banner("nogic watch", str(directory))
        ui.kv("Project", f"{config.project_id[:8]}...")

    log_fn = (lambda msg: None) if json_mode else (lambda msg: ui.dim(f"  {msg}"))
    sync_service = SyncService(config, directory, log=log_fn, json_mode=json_mode)

    should_ignore = build_ignore_matcher(directory, extra_patterns=ignore)

    # Initial scan
    try:
        sync_service.initial_scan(directory, should_ignore)
        if json_mode:
            files_indexed = len(sync_service._file_cache)
            _emit_json("initial_scan_complete", files_indexed=files_indexed)
    except KeyboardInterrupt:
        if not json_mode:
            ui.console.print()
            ui.dim("Interrupted during initial scan. Cleaning up...")
        try:
            sync_service.client.clear_staging(config.project_id)
        except Exception:
            pass
        sync_service.close()
        raise typer.Exit(1)

    def on_change(path: Path):
        try:
            rel = path.relative_to(directory)
        except ValueError:
            return
        try:
            if sync_service.sync_file_immediate(path):
                if json_mode:
                    _emit_json("synced", path=str(rel))
                else:
                    ui.console.print(f"  [green]SYNCED[/] {rel}")
        except Exception as e:
            err_msg = str(e)
            if "413" in err_msg:
                if json_mode:
                    _emit_json("skip", path=str(rel), reason="file too large")
                else:
                    ui.console.print(f"  [yellow]SKIP[/] {rel} (file too large for API)")
            elif "503" in err_msg or "502" in err_msg:
                if json_mode:
                    _emit_json("error", path=str(rel), message="backend unavailable")
                else:
                    ui.console.print(f"  [red]ERROR[/] {rel} (backend unavailable, will sync on next change)")
            else:
                if json_mode:
                    _emit_json("error", path=str(rel), message=err_msg[:120])
                else:
                    ui.console.print(f"  [red]ERROR[/] {rel}: {err_msg[:120]}")

    def on_delete(path: Path):
        try:
            rel = path.relative_to(directory)
        except ValueError:
            return
        try:
            if sync_service.delete_file_immediate(path):
                if json_mode:
                    _emit_json("deleted", path=str(rel))
                else:
                    ui.console.print(f"  [red]DELETED[/] {rel}")
            else:
                if json_mode:
                    _emit_json("deleted", path=str(rel))
                else:
                    ui.console.print(f"  [dim]DELETED[/] {rel} (not indexed)")
        except Exception as e:
            if json_mode:
                _emit_json("error", path=str(rel), message=str(e)[:80])
            else:
                ui.console.print(f"  [red]DELETED[/] {rel} (error: {str(e)[:80]})")

    monitor = FileMonitor(
        root_path=directory,
        on_change=on_change,
        on_delete=on_delete,
        should_ignore=should_ignore,
    )

    if json_mode:
        _emit_json("ready", message="Watching for changes...")
    else:
        ui.console.print()
        ui.info("Watching for changes... (Ctrl+C to stop)")
        ui.console.print()

    monitor.start()
    try:
        while monitor.is_alive():
            time.sleep(1)
    except KeyboardInterrupt:
        if not json_mode:
            ui.console.print()
            ui.dim("Stopping...")
    finally:
        monitor.stop()
        sync_service.close()

    if not json_mode:
        ui.success("Done.")
