"""Directory polling file watcher that detects new files."""

import fnmatch
import json
import logging
import os
import threading
import time
import urllib.request


class FileWatcher:
    """Watch a directory for new files by polling at a fixed interval.

    Args:
        directory: Path to the directory to watch.
        interval: Seconds between polls (default 5.0).
    """

    def __init__(self, directory: str, interval: float = 5.0) -> None:
        self.directory = directory
        self.interval = interval
        self._seen_files: set[str] = set()
        self._patterns: list[dict[str, str]] = []
        self._processed_log: list[dict] = []
        self._stop_event = threading.Event()
        self._logger = logging.getLogger(__name__)
        self._error_count = 0
        self._start_time: float | None = None

    def register_pattern(self, glob_pattern: str, chain_template: str) -> None:
        """Register a glob pattern mapped to a chain template.

        Args:
            glob_pattern: Glob pattern to match against new files.
            chain_template: Chain template to execute when a file matches.
        """
        self._patterns.append({"pattern": glob_pattern, "chain_template": chain_template})

    def on_new_file(self, filepath: str) -> None:
        """Match filename against registered patterns and process matches.

        For each registered pattern that matches the filename (via fnmatch),
        reads the file content and POSTs it to the local runtime chat endpoint
        with the associated chain_template. Results are logged to _processed_log.
        """
        filename = os.path.basename(filepath)
        for entry in self._patterns:
            if not fnmatch.fnmatch(filename, entry["pattern"]):
                continue
            try:
                with open(filepath, "r", encoding="utf-8") as f:
                    content = f.read()
            except OSError as exc:
                self._logger.error("Failed to read %s: %s", filepath, exc)
                self._error_count += 1
                continue
            payload = json.dumps({
                "message": content,
                "chain_template": entry["chain_template"],
            }).encode("utf-8")
            req = urllib.request.Request(
                "http://localhost:8003/chat",
                data=payload,
                headers={"Content-Type": "application/json"},
                method="POST",
            )
            try:
                with urllib.request.urlopen(req) as resp:
                    result = json.loads(resp.read().decode("utf-8"))
            except Exception as exc:
                self._logger.error("POST /chat failed for %s: %s", filepath, exc)
                self._error_count += 1
                result = {"error": str(exc)}
            status = "error" if "error" in result else "success"
            log_entry = {
                "filepath": filepath,
                "pattern_matched": entry["pattern"],
                "chain_template": entry["chain_template"],
                "status": status,
                "timestamp": time.time(),
                "result": result,
            }
            self._processed_log.append(log_entry)
            self._logger.info("Processed %s with template %s", filepath, entry["chain_template"])

    def get_processed_log(self, limit: int = 50) -> list[dict]:
        """Return the last *limit* entries from the processed log.

        Each entry contains: filepath, pattern_matched, chain_template,
        status, and timestamp.

        Args:
            limit: Maximum number of entries to return (default 50).
        """
        return [
            {
                "filepath": e["filepath"],
                "pattern_matched": e["pattern_matched"],
                "chain_template": e["chain_template"],
                "status": e["status"],
                "timestamp": e["timestamp"],
            }
            for e in self._processed_log[-limit:]
        ]

    def stop(self) -> None:
        """Signal the polling loop to exit."""
        self._stop_event.set()
        self._logger.info("FileWatcher stopped")

    def get_status(self) -> dict:
        """Return current watcher status.

        Returns:
            Dict with watching, directory, patterns, files_processed,
            errors, and uptime_seconds.
        """
        watching = self._start_time is not None and not self._stop_event.is_set()
        uptime = (
            time.monotonic() - self._start_time
            if self._start_time is not None
            else 0.0
        )
        return {
            "watching": watching,
            "directory": self.directory,
            "patterns": [e["pattern"] for e in self._patterns],
            "files_processed": len(self._processed_log),
            "errors": self._error_count,
            "uptime_seconds": round(uptime, 2),
        }

    def _poll(self) -> None:
        """Continuously poll the directory for new files."""
        while not self._stop_event.is_set():
            try:
                current = set(os.listdir(self.directory))
            except OSError:
                if self._stop_event.wait(self.interval):
                    break
                continue
            new = current - self._seen_files
            for name in sorted(new):
                filepath = os.path.join(self.directory, name)
                self.on_new_file(filepath)
            self._seen_files = current
            if self._stop_event.wait(self.interval):
                break

    def start(self) -> None:
        """Start the polling loop in a daemon thread."""
        self._start_time = time.monotonic()
        # Seed with existing files so only truly new files trigger callbacks
        try:
            self._seen_files = set(os.listdir(self.directory))
        except OSError:
            self._seen_files = set()
        t = threading.Thread(target=self._poll, daemon=True)
        t.start()
