"""File watcher for spec changes during development.

Uses polling-based file monitoring to detect spec file changes and
trigger automatic regeneration. No external dependencies required.
"""

from __future__ import annotations

import subprocess
import sys
import threading
import time
from pathlib import Path
from typing import TYPE_CHECKING

from rich.console import Console

if TYPE_CHECKING:
    from prisme.config.schema import WatchConfig

console = Console(stderr=True)


class SpecFileWatcher:
    """Watches spec files for changes and triggers regeneration.

    Uses a polling loop with file modification time tracking.
    Debounces rapid changes to avoid excessive regeneration.
    """

    def __init__(
        self,
        project_dir: Path,
        watch_config: WatchConfig | None = None,
        on_change: str | None = None,
    ) -> None:
        self.project_dir = project_dir
        self._stop_event = threading.Event()
        self._thread: threading.Thread | None = None
        self._last_mtimes: dict[str, float] = {}
        self._regenerating = False

        # Load config or use defaults
        if watch_config:
            self.patterns = watch_config.patterns
            self.debounce = watch_config.debounce_seconds
            self.auto_regenerate = watch_config.auto_regenerate
            self.run_migrations = watch_config.run_migrations
        else:
            self.patterns = ["specs/**/*.py", "prisme.toml"]
            self.debounce = 1.0
            self.auto_regenerate = True
            self.run_migrations = True

        # Initialize file modification times
        self._scan_files()

    def _match_patterns(self) -> list[Path]:
        """Find all files matching the watch patterns."""
        matched: list[Path] = []
        for pattern in self.patterns:
            if "**" in pattern or "*" in pattern:
                matched.extend(self.project_dir.glob(pattern))
            else:
                # Exact path
                p = self.project_dir / pattern
                if p.exists():
                    matched.append(p)
        return [f for f in matched if f.is_file()]

    def _scan_files(self) -> dict[str, float]:
        """Scan all watched files and return their modification times."""
        mtimes: dict[str, float] = {}
        for f in self._match_patterns():
            try:
                mtimes[str(f)] = f.stat().st_mtime
            except OSError:
                continue
        return mtimes

    def _detect_changes(self) -> list[str]:
        """Detect which watched files have changed since last scan."""
        current = self._scan_files()
        changed: list[str] = []

        for path, mtime in current.items():
            if path not in self._last_mtimes or self._last_mtimes[path] < mtime:
                changed.append(path)

        # Also detect deleted files
        for path in self._last_mtimes:
            if path not in current:
                changed.append(path)

        self._last_mtimes = current
        return changed

    def _regenerate(self, changed_files: list[str]) -> None:
        """Run prisme generate after detecting spec changes."""
        if self._regenerating:
            return
        self._regenerating = True

        try:
            # Show which files changed
            rel_paths = []
            for f in changed_files:
                try:
                    rel_paths.append(str(Path(f).relative_to(self.project_dir)))
                except ValueError:
                    rel_paths.append(f)

            console.print(
                f"[bold yellow][Auto-regen][/] Detected changes in: {', '.join(rel_paths)}"
            )

            if self.auto_regenerate:
                console.print("[bold yellow][Auto-regen][/] Regenerating...")
                result = subprocess.run(
                    [sys.executable, "-m", "prisme", "generate"],
                    cwd=self.project_dir,
                    capture_output=True,
                    text=True,
                )
                if result.returncode == 0:
                    console.print("[bold green][Auto-regen][/] Regeneration complete")
                else:
                    console.print(
                        f"[bold red][Auto-regen][/] Regeneration failed: {result.stderr[:200]}"
                    )

                # Run migrations if configured
                if self.run_migrations and result.returncode == 0:
                    console.print("[bold yellow][Auto-regen][/] Running migrations...")
                    migrate_result = subprocess.run(
                        [sys.executable, "-m", "prisme", "db", "migrate", "-m", "auto"],
                        cwd=self.project_dir,
                        capture_output=True,
                        text=True,
                    )
                    if migrate_result.returncode == 0:
                        console.print("[bold green][Auto-regen][/] Migrations complete")
                    else:
                        # Migration failure is not critical (might not have changes)
                        console.print("[dim][Auto-regen] No migration changes detected[/]")
        finally:
            self._regenerating = False

    def _watch_loop(self) -> None:
        """Main watch loop that polls for file changes."""
        while not self._stop_event.is_set():
            try:
                changed = self._detect_changes()
                if changed:
                    # Debounce: wait a bit to batch rapid changes
                    time.sleep(self.debounce)
                    # Re-check to catch any additional changes during debounce
                    more_changed = self._detect_changes()
                    changed.extend(more_changed)
                    # Deduplicate
                    changed = list(dict.fromkeys(changed))
                    self._regenerate(changed)
            except Exception as e:
                console.print(f"[dim][Watcher] Error: {e}[/]")

            # Poll interval
            self._stop_event.wait(1.0)

    def start(self) -> None:
        """Start watching for file changes in a background thread."""
        if self._thread and self._thread.is_alive():
            return

        patterns_display = ", ".join(self.patterns)
        console.print(f"[green]  Watching for spec changes: {patterns_display}[/]")

        self._thread = threading.Thread(target=self._watch_loop, daemon=True)
        self._thread.start()

    def stop(self) -> None:
        """Stop the file watcher."""
        self._stop_event.set()
        if self._thread:
            self._thread.join(timeout=2)


__all__ = ["SpecFileWatcher"]
