from __future__ import annotations

import mimetypes
import os
from importlib import resources
from pathlib import Path
from typing import Any

from fastapi import File, Form, HTTPException, UploadFile
from fastapi.responses import FileResponse, HTMLResponse

from ..agent import A2AAgent
from ..frontend import resolve_packed_frontend
from ..serve.asgi import build_app
from .local import ensure_local_workspace, load_env_file, load_local_project


def create_app():
    project = Path(os.environ.get("A2A_PROJECT_DIR", ".")).resolve()
    _ = os.environ["A2A_ENTRYPOINT"]
    env_file = Path(os.environ.get("A2A_ENV_FILE", project / ".env.local"))
    load_env_file(env_file)
    workspace = ensure_local_workspace(
        project,
        workspace=Path(os.environ["A2A_LOCAL_WORKSPACE_DIR"])
        if os.environ.get("A2A_LOCAL_WORKSPACE_DIR")
        else None,
    )
    os.environ.setdefault("A2A_LOCAL_DEV", "1")
    os.environ.setdefault("A2A_LOCAL_WORKSPACE_DIR", str(workspace))
    local = load_local_project(project)
    agent = local.agent_cls()
    frontend = resolve_packed_frontend(project, local.config)
    app = build_app(agent, frontend=frontend)
    _mount_dev_ui(app, agent, workspace)
    return app


def _mount_dev_ui(app: Any, agent: A2AAgent, workspace: Path) -> None:
    static_root = resources.files("a2a_pack.cli.dev_ui_static")
    workspace_root = workspace.resolve()

    @app.get("/_dev", include_in_schema=False)
    @app.get("/_dev/", include_in_schema=False)
    async def dev_ui() -> HTMLResponse:
        return HTMLResponse(
            static_root.joinpath("index.html").read_text(encoding="utf-8")
        )

    @app.get("/_dev/assets/{path:path}", include_in_schema=False)
    async def dev_asset(path: str) -> FileResponse:
        target = _safe_static_path(static_root, path)
        if not target.is_file():
            raise HTTPException(404, "asset not found")
        return FileResponse(
            target,
            media_type=mimetypes.guess_type(target.name)[0]
            or "application/octet-stream",
        )

    @app.get("/_dev/api/card", include_in_schema=False)
    async def dev_card() -> dict[str, Any]:
        return agent.card().model_dump(mode="json")

    @app.get("/_dev/api/files", include_in_schema=False)
    async def dev_files() -> list[dict[str, Any]]:
        return _list_workspace_files(workspace_root)

    @app.post("/_dev/api/files", include_in_schema=False)
    async def dev_upload_file(
        file: UploadFile = File(...),
        prefix: str = Form("inputs"),
    ) -> dict[str, Any]:
        filename = Path(file.filename or "upload.bin").name
        if not filename:
            raise HTTPException(400, "missing filename")
        rel = f"{prefix.strip('/')}/{filename}" if prefix.strip("/") else filename
        target = _safe_workspace_path(workspace_root, rel)
        target.parent.mkdir(parents=True, exist_ok=True)
        target.write_bytes(await file.read())
        return _file_meta(workspace_root, target)

    @app.get("/_dev/api/files/{path:path}", include_in_schema=False)
    async def dev_download_file(path: str) -> FileResponse:
        target = _safe_workspace_path(workspace_root, path)
        if not target.is_file():
            raise HTTPException(404, "file not found")
        return FileResponse(
            target,
            filename=target.name,
            media_type=mimetypes.guess_type(target.name)[0]
            or "application/octet-stream",
        )


def _list_workspace_files(root: Path) -> list[dict[str, Any]]:
    root.mkdir(parents=True, exist_ok=True)
    return sorted(
        (_file_meta(root, path) for path in root.rglob("*") if path.is_file()),
        key=lambda item: item["path"],
    )


def _file_meta(root: Path, path: Path) -> dict[str, Any]:
    stat = path.stat()
    rel = path.resolve().relative_to(root).as_posix()
    return {
        "path": rel,
        "size_bytes": stat.st_size,
        "content_type": mimetypes.guess_type(path.name)[0]
        or "application/octet-stream",
        "updated_at": stat.st_mtime,
    }


def _safe_workspace_path(root: Path, relpath: str) -> Path:
    clean = relpath.replace("\\", "/").lstrip("/")
    if not clean or clean == ".":
        raise HTTPException(400, "path is required")
    target = (root / clean).resolve()
    try:
        target.relative_to(root)
    except ValueError as exc:
        raise HTTPException(400, "unsafe workspace path") from exc
    return target


def _safe_static_path(root: Any, relpath: str) -> Path:
    clean = relpath.replace("\\", "/").lstrip("/")
    if not clean or clean == ".":
        raise HTTPException(400, "path is required")
    base = Path(str(root)).resolve()
    target = (base / clean).resolve()
    try:
        target.relative_to(base)
    except ValueError as exc:
        raise HTTPException(400, "unsafe asset path") from exc
    return target
