import asyncio
import logging
import os
from pathlib import Path

from fastapi import APIRouter, FastAPI
from starlette.responses import StreamingResponse

from nextpy_server.build.run import build_css, run_build, write_config

logger = logging.getLogger("n6k.dev")
logger.setLevel(logging.DEBUG)
if not logger.handlers:
    _handler = logging.StreamHandler()
    _handler.setFormatter(logging.Formatter("%(levelname)s:     [n6k] %(message)s"))
    logger.addHandler(_handler)

PROJECT_DIR = Path(".")

_DEV_SCRIPT_TEMPLATE = """\
<script>
(function() {{
  var es = new EventSource("{asset_prefix}/events");
  var wasConnected = false;
  es.addEventListener("reload", function() {{ location.reload(); }});
  es.onopen = function() {{
    if (wasConnected) {{ location.reload(); }}
    wasConnected = true;
  }};
}})();
</script>"""


def dev_script(asset_prefix: str) -> str:
    return _DEV_SCRIPT_TEMPLATE.format(asset_prefix=asset_prefix)


_sse_queues: set[asyncio.Queue] = set()


async def _sse_generator(queue: asyncio.Queue):
    _sse_queues.add(queue)
    try:
        while True:
            data = await queue.get()
            yield f"event: reload\ndata: {data}\n\n"
    except asyncio.CancelledError:
        pass
    finally:
        _sse_queues.discard(queue)


dev_router = APIRouter()


@dev_router.get("/events")
async def sse_events():
    queue: asyncio.Queue = asyncio.Queue()
    return StreamingResponse(
        _sse_generator(queue),
        media_type="text/event-stream",
        headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
    )


async def startup_rebuild(app: FastAPI):
    """Rebuild CSS/JS from current app state."""
    bindings = app.state.bindings
    app_dir = getattr(app.state, "_n6k_source_dir", None)
    entry_points = getattr(app.state, "_n6k_entry_points", None)

    config_fn = getattr(app.state, "_config_fn", None)

    logger.debug("Writing n6k.config.json...")
    await asyncio.to_thread(
        write_config,
        PROJECT_DIR,
        bindings=bindings,
        app_dir=app_dir,
        entry_points=entry_points,
        config_fn=config_fn,
    )

    logger.debug("Building CSS...")
    await asyncio.to_thread(build_css, PROJECT_DIR)

    logger.debug("Building JS...")
    result = await asyncio.to_thread(run_build, PROJECT_DIR)
    if result.returncode != 0:
        logger.error(f"Startup build failed (rc={result.returncode}):\n{result.stderr}")
    else:
        logger.info("Startup build complete")


async def start_watcher():
    """Watch UI source files and rebuild on change."""
    from watchfiles import awatch

    source_dir = os.environ.get("N6K_APP_DIR")
    source_path = Path(source_dir) if source_dir else None

    watch_dirs = [PROJECT_DIR / "src" / d for d in ("components", "lib")]
    watch_files = [PROJECT_DIR / "src" / "app" / "globals.css"]

    if source_path:
        watch_dirs.append(source_path)

    watch_paths = [p for p in watch_dirs + watch_files if p.exists()]

    if not watch_paths:
        logger.warning("No watch paths found, file watcher not started")
        return

    logger.info(f"Watching: {[str(p) for p in watch_paths]}")

    try:
        async for changes in awatch(*watch_paths):
            # Skip .py files (uvicorn handles those) and __astro__/ (build output)
            non_py_changes = [
                (change_type, path)
                for change_type, path in changes
                if not path.endswith(".py") and "/__astro__/" not in path
            ]
            if not non_py_changes:
                continue

            changed_files = [str(path) for _, path in non_py_changes]
            logger.info(f"File change detected: {changed_files}")

            try:
                logger.debug("Rebuilding CSS...")
                await asyncio.to_thread(build_css, PROJECT_DIR)
                logger.debug("Rebuilding JS...")
                result = await asyncio.to_thread(run_build, PROJECT_DIR)
                if result.returncode != 0:
                    logger.error(f"Build failed (rc={result.returncode}):\n{result.stderr}")
                    continue
                n_clients = len(_sse_queues)
                logger.info(f"Build succeeded, sending reload to {n_clients} client(s)")
                for queue in set(_sse_queues):
                    queue.put_nowait("reload")
            except Exception as e:
                logger.error(f"Rebuild error: {e}")
    except asyncio.CancelledError:
        pass


def register_dev(app: FastAPI):
    """Register SSE endpoint for dev mode live reload."""
    app.include_router(dev_router, prefix=app.state.asset_prefix)
