"""
Minimal FastAPI server for the local viewer.

Serves GET /api/runs, GET /api/runs/{run_id}, GET /api/runs/{run_id}/events,
and GET / with static index.html. No CORS by default.
Config is loaded once at app creation and cached on app.state.
"""

from pathlib import Path

from fastapi import Depends, FastAPI, HTTPException, Request, Response
from fastapi.responses import FileResponse
from pydantic import BaseModel

import agentdbg.storage as storage
from agentdbg.config import AgentDbgConfig, load_config
from agentdbg.constants import SPEC_VERSION

UI_STATIC_DIR = Path(__file__).resolve().parent / "ui_static"
UI_INDEX_PATH = UI_STATIC_DIR / "index.html"
UI_STYLES_PATH = UI_STATIC_DIR / "styles.css"
UI_APP_JS_PATH = UI_STATIC_DIR / "app.js"
FAVICON_PATH = UI_STATIC_DIR / "favicon.svg"


def _get_config(request: Request) -> AgentDbgConfig:
    """Return config cached on app state (set at app creation)."""
    return request.app.state.config


def create_app() -> FastAPI:
    """Create and return the FastAPI application for the local viewer."""
    app = FastAPI(title="AgentDbg Viewer")
    app.state.config = load_config()

    class RenameRunRequest(BaseModel):
        run_name: str

    @app.get("/api/runs")
    def get_runs(config: AgentDbgConfig = Depends(_get_config)) -> dict:
        """List recent runs. Response: { spec_version, runs }."""
        runs = storage.list_runs(limit=50, config=config)
        return {"spec_version": SPEC_VERSION, "runs": runs}

    @app.get("/api/runs/{run_id}")
    def get_run_meta(
        run_id: str, config: AgentDbgConfig = Depends(_get_config)
    ) -> dict:
        """Return run.json metadata for the given run_id."""
        try:
            return storage.load_run_meta(run_id, config)
        except ValueError:
            raise HTTPException(status_code=400, detail="invalid run_id")
        except FileNotFoundError:
            raise HTTPException(status_code=404, detail="run not found")

    @app.get("/api/runs/{run_id}/events")
    def get_run_events(
        run_id: str, config: AgentDbgConfig = Depends(_get_config)
    ) -> dict:
        """Return events array for the run. 404 if run not found."""
        try:
            storage.load_run_meta(run_id, config)
        except ValueError:
            raise HTTPException(status_code=400, detail="invalid run_id")
        except FileNotFoundError:
            raise HTTPException(status_code=404, detail="run not found")
        try:
            events = storage.load_events(run_id, config)
        except ValueError:
            raise HTTPException(status_code=400, detail="invalid run_id")
        return {
            "spec_version": SPEC_VERSION,
            "run_id": run_id,
            "events": events,
        }

    @app.get("/api/runs/{run_id}/paths")
    def get_run_paths(
        run_id: str, config: AgentDbgConfig = Depends(_get_config)
    ) -> dict:
        """Return local filesystem paths for the run (run_dir, run_json, events_jsonl)."""
        try:
            paths = storage.get_run_paths(run_id, config)
        except ValueError:
            raise HTTPException(status_code=400, detail="invalid run_id")
        except FileNotFoundError:
            raise HTTPException(status_code=404, detail="run not found")
        return {"spec_version": SPEC_VERSION, "run_id": run_id, "paths": paths}

    @app.get("/api/runs/{run_id}/rename")
    def validate_run_for_rename(
        run_id: str, config: AgentDbgConfig = Depends(_get_config)
    ) -> dict:
        """
        Validate run_id and return metadata.

        Primarily exists so path-traversal tests can exercise the validator on this
        endpoint with GET, matching /api/runs/{run_id}.
        """
        try:
            return storage.load_run_meta(run_id, config)
        except ValueError:
            raise HTTPException(status_code=400, detail="invalid run_id")
        except FileNotFoundError:
            raise HTTPException(status_code=404, detail="run not found")

    @app.post("/api/runs/{run_id}/rename")
    def rename_run(
        run_id: str,
        payload: RenameRunRequest,
        config: AgentDbgConfig = Depends(_get_config),
    ) -> dict:
        """Rename a run by updating its run.json run_name field."""
        try:
            return storage.rename_run(run_id, payload.run_name, config)
        except ValueError as e:
            msg = str(e)
            detail = "invalid run_id" if "invalid run_id" in msg else msg
            raise HTTPException(status_code=400, detail=detail)
        except FileNotFoundError:
            raise HTTPException(status_code=404, detail="run not found")

    @app.delete("/api/runs/{run_id}")
    def delete_run(
        run_id: str, config: AgentDbgConfig = Depends(_get_config)
    ) -> Response:
        """Delete a run directory and its contents."""
        try:
            storage.delete_run(run_id, config)
        except ValueError:
            raise HTTPException(status_code=400, detail="invalid run_id")
        except FileNotFoundError:
            raise HTTPException(status_code=404, detail="run not found")
        return Response(status_code=204)

    @app.get("/favicon.svg")
    def serve_favicon() -> FileResponse:
        """Serve favicon to avoid 404 and improve polish."""
        if not FAVICON_PATH.is_file():
            raise HTTPException(status_code=404, detail="favicon not found")
        return FileResponse(FAVICON_PATH, media_type="image/svg+xml")

    @app.get("/styles.css")
    def serve_styles() -> Response:
        """Serve UI stylesheet."""
        if not UI_STYLES_PATH.is_file():
            raise HTTPException(status_code=404, detail="styles not found")
        response = FileResponse(UI_STYLES_PATH, media_type="text/css")
        response.headers["Cache-Control"] = "no-cache"
        return response

    @app.get("/app.js")
    def serve_app_js() -> Response:
        """Serve UI application script."""
        if not UI_APP_JS_PATH.is_file():
            raise HTTPException(status_code=404, detail="app.js not found")
        response = FileResponse(UI_APP_JS_PATH, media_type="application/javascript")
        response.headers["Cache-Control"] = "no-cache"
        return response

    @app.get("/")
    def serve_ui() -> Response:
        """Serve the static HTML UI with content-type text/html."""
        if not UI_INDEX_PATH.is_file():
            raise HTTPException(
                status_code=404,
                detail="UI not found: agentdbg/ui_static/index.html is missing",
            )
        response = FileResponse(UI_INDEX_PATH, media_type="text/html")
        response.headers["Cache-Control"] = "no-cache"
        return response

    return app
