"""File watcher client: monitors code2llm output and pushes deltas to the proxy.

Usage:
  proxym-client --watch ./project --proxy http://localhost:4000

Or programmatically:
  from proxym.watch.client import DeltaWatchClient
  client = DeltaWatchClient("./project", "http://localhost:4000")
  await client.run()
"""

from __future__ import annotations

import asyncio
import argparse
import sys
import time

import httpx
import structlog
from watchfiles import awatch, Change

from proxym.cache import DeltaBuffer

logger = structlog.get_logger()


class DeltaWatchClient:
    """Watches a directory and pushes context deltas to the proxy server."""

    def __init__(
        self,
        watch_dir: str = "./project",
        proxy_url: str = "http://localhost:4000",
        api_key: str = "sk-proxy-local-dev",
        debounce_ms: int = 1000,
        max_context_tokens: int = 120_000,
    ):
        self.proxy_url = proxy_url.rstrip("/")
        self.api_key = api_key
        self.debounce_ms = debounce_ms
        self.buffer = DeltaBuffer(
            watch_dir=watch_dir,
            max_context_tokens=max_context_tokens,
        )
        self._http = httpx.AsyncClient(timeout=30)

    async def push_delta(self) -> dict | None:
        """Compute delta and push to proxy server."""
        delta = self.buffer.compute_delta()

        payload = {
            "version": delta.version,
            "files_changed": delta.files_changed,
            "files_added": delta.files_added,
            "files_removed": delta.files_removed,
            "files_total": delta.files_total,
            "is_full_refresh": delta.is_full_refresh,
            "token_estimate": delta.token_estimate,
            "payload": delta.payload,
        }

        try:
            resp = await self._http.post(
                f"{self.proxy_url}/v1/context/delta",
                json=payload,
                headers={"Authorization": f"Bearer {self.api_key}"},
            )
            resp.raise_for_status()
            result = resp.json()
            logger.info(
                "delta_pushed",
                version=delta.version,
                is_full=delta.is_full_refresh,
                tokens=delta.token_estimate,
                changed=delta.files_changed,
            )
            return result
        except httpx.HTTPError as e:
            logger.error("delta_push_failed", error=str(e))
            return None

    async def run(self) -> None:
        """Watch directory and push deltas on changes."""
        logger.info("watcher_started", dir=str(self.buffer.watch_dir))

        # Initial full push
        await self.push_delta()

        # Watch for changes
        async for changes in awatch(
            self.buffer.watch_dir,
            debounce=self.debounce_ms,
            recursive=True,
        ):
            relevant = False
            for change_type, path in changes:
                from pathlib import Path
                p = Path(path)
                if p.suffix in self.buffer.extensions:
                    relevant = True
                    change_name = {Change.added: "added", Change.modified: "modified",
                                   Change.deleted: "deleted"}.get(change_type, "unknown")
                    logger.debug("file_changed", path=str(p.name), type=change_name)

            if relevant:
                await self.push_delta()

    async def close(self) -> None:
        await self._http.aclose()


def cli():
    """CLI entry point for proxym-client."""
    parser = argparse.ArgumentParser(description="Watch code2llm output and push deltas")
    parser.add_argument("--watch", "-w", default="./project", help="Directory to watch")
    parser.add_argument("--proxy", "-p", default="http://localhost:4000", help="Proxy URL")
    parser.add_argument("--key", "-k", default="sk-proxy-local-dev", help="API key")
    parser.add_argument("--debounce", type=int, default=1000, help="Debounce ms")
    parser.add_argument("--max-tokens", type=int, default=120_000, help="Max context tokens")
    args = parser.parse_args()

    client = DeltaWatchClient(
        watch_dir=args.watch,
        proxy_url=args.proxy,
        api_key=args.key,
        debounce_ms=args.debounce,
        max_context_tokens=args.max_tokens,
    )

    try:
        asyncio.run(client.run())
    except KeyboardInterrupt:
        logger.info("watcher_stopped")
        asyncio.run(client.close())


if __name__ == "__main__":
    cli()
