"""The mgf-cloud web application — FastAPI routes + Jinja rendering.

A **local, single-user** operator console: it binds 127.0.0.1, has no auth, and
drives the same :class:`~mgf.cloud.providers.base.CloudProvider` as the CLI, so it
manages any provider's resources. Wired with the mgf-fastapi ``RequestIdMiddleware``;
``mgf.common`` ``AppError``\\ s render an HTML error page.
"""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING

from fastapi import FastAPI, File, Form, Request, UploadFile
from fastapi.responses import HTMLResponse, RedirectResponse, Response
from fastapi.templating import Jinja2Templates
from mgf.common.exceptions import AppError, ResourceAlreadyExistsError, ResourceNotFoundError
from mgf.common.observability import get_logger
from mgf.fastapi import RequestIdMiddleware

from mgf.cloud.cost import estimate, rates_from_config
from mgf.cloud.models import ServerSpec, VolumeSpec, owned_metadata

if TYPE_CHECKING:
    from mgf.cloud.providers.base import CloudProvider

LOG = get_logger(__name__)

_TEMPLATES = Path(__file__).parent / "templates"
_REDIRECT = 303


def create_app(provider: CloudProvider) -> FastAPI:
    """Build the FastAPI app bound to ``provider``."""
    app = FastAPI(title="mgf-cloud", docs_url=None, redoc_url=None)
    app.add_middleware(RequestIdMiddleware)
    templates = Jinja2Templates(directory=str(_TEMPLATES))

    def render(request: Request, name: str, **ctx: object) -> Response:
        return templates.TemplateResponse(request, name, ctx)

    @app.exception_handler(AppError)
    def _on_app_error(request: Request, exc: Exception) -> Response:
        status = (
            404
            if isinstance(exc, ResourceNotFoundError)
            else (409 if isinstance(exc, ResourceAlreadyExistsError) else 400)
        )
        return templates.TemplateResponse(
            request, "error.html", {"error": str(exc), "status": status}, status_code=status
        )

    @app.get("/", response_class=HTMLResponse)
    def dashboard(request: Request) -> Response:
        vms = provider.list_servers()
        volumes = provider.list_volumes()
        fips = provider.list_floating_ips()
        cost = estimate(
            servers=list(vms),
            flavors=list(provider.list_flavors()),
            volumes=list(volumes),
            floating_ips=list(fips),
            rates=rates_from_config(),
        )
        return render(
            request,
            "dashboard.html",
            vms=vms,
            volumes=volumes,
            fips=fips,
            containers=provider.list_containers(),
            secgroups=provider.list_security_groups(),
            keypairs=provider.list_keypairs(),
            cost=cost,
        )

    @app.get("/vms", response_class=HTMLResponse)
    def vms(request: Request) -> Response:
        return render(request, "vms.html", vms=provider.list_servers())

    @app.get("/vms/new", response_class=HTMLResponse)
    def vm_new(request: Request) -> Response:
        return render(
            request,
            "vm_new.html",
            flavors=provider.list_flavors(),
            images=provider.list_images(),
            networks=provider.list_networks(),
        )

    @app.post("/vms")
    def vm_create(
        name: str = Form(...),
        flavor: str = Form(...),
        image: str = Form(...),
        network: str = Form(""),
        floating_ip: bool = Form(False),
    ) -> Response:
        provider.create_server(
            ServerSpec(
                name=name,
                flavor=flavor,
                image=image,
                network=network or None,
                assign_floating_ip=floating_ip,
                metadata=owned_metadata(),
            )
        )
        return RedirectResponse("/vms", status_code=_REDIRECT)

    @app.get("/vms/{name}", response_class=HTMLResponse)
    def vm_detail(request: Request, name: str) -> Response:
        return render(request, "vm_detail.html", vm=provider.get_server(name))

    @app.post("/vms/{name}/delete")
    def vm_delete(name: str) -> Response:
        provider.delete_server(name)
        return RedirectResponse("/vms", status_code=_REDIRECT)

    @app.post("/vms/{name}/reboot")
    def vm_reboot(name: str) -> Response:
        provider.reboot_server(name)
        return RedirectResponse(f"/vms/{name}", status_code=_REDIRECT)

    @app.get("/volumes", response_class=HTMLResponse)
    def volumes(request: Request) -> Response:
        return render(request, "volumes.html", volumes=provider.list_volumes())

    @app.post("/volumes")
    def volume_create(name: str = Form(...), size: int = Form(...)) -> Response:
        provider.create_volume(VolumeSpec(name=name, size_gb=size))
        return RedirectResponse("/volumes", status_code=_REDIRECT)

    @app.post("/volumes/{name}/delete")
    def volume_delete(name: str) -> Response:
        provider.delete_volume(name)
        return RedirectResponse("/volumes", status_code=_REDIRECT)

    @app.get("/floating-ips", response_class=HTMLResponse)
    def fips(request: Request) -> Response:
        return render(request, "floating_ips.html", fips=provider.list_floating_ips())

    @app.post("/floating-ips")
    def fip_create(network: str = Form("")) -> Response:
        provider.create_floating_ip(network or None)
        return RedirectResponse("/floating-ips", status_code=_REDIRECT)

    @app.post("/floating-ips/{address}/release")
    def fip_release(address: str) -> Response:
        provider.release_floating_ip(address)
        return RedirectResponse("/floating-ips", status_code=_REDIRECT)

    @app.get("/objects", response_class=HTMLResponse)
    def objects(request: Request) -> Response:
        return render(request, "objects.html", containers=provider.list_containers())

    @app.get("/security-groups", response_class=HTMLResponse)
    def secgroups(request: Request) -> Response:
        return render(request, "secgroups.html", secgroups=provider.list_security_groups())

    @app.get("/keypairs", response_class=HTMLResponse)
    def keypairs(request: Request) -> Response:
        return render(request, "keypairs.html", keypairs=provider.list_keypairs())

    # --- object storage management ---
    @app.post("/objects")
    def container_create(name: str = Form(...)) -> Response:
        provider.create_container(name)
        return RedirectResponse("/objects", status_code=_REDIRECT)

    @app.post("/objects/{container}/delete")
    def container_delete(container: str) -> Response:
        provider.delete_container(container)
        return RedirectResponse("/objects", status_code=_REDIRECT)

    @app.get("/objects/{container}", response_class=HTMLResponse)
    def container_detail(request: Request, container: str) -> Response:
        return render(
            request,
            "object_detail.html",
            container=container,
            objects=provider.list_objects(container),
        )

    @app.post("/objects/{container}/upload")
    async def object_upload(container: str, file: UploadFile = File(...)) -> Response:
        data = await file.read()
        provider.put_object(
            container,
            file.filename or "upload",
            data,
            file.content_type or "application/octet-stream",
        )
        return RedirectResponse(f"/objects/{container}", status_code=_REDIRECT)

    @app.get("/objects/{container}/{name}/download")
    def object_download(container: str, name: str) -> Response:
        data = provider.get_object(container, name)
        return Response(
            content=data,
            media_type="application/octet-stream",
            headers={"Content-Disposition": f'attachment; filename="{name}"'},
        )

    @app.post("/objects/{container}/{name}/delete")
    def object_delete(container: str, name: str) -> Response:
        provider.delete_object(container, name)
        return RedirectResponse(f"/objects/{container}", status_code=_REDIRECT)

    # --- security groups ---
    @app.post("/security-groups")
    def secgroup_create(name: str = Form(...), description: str = Form("")) -> Response:
        provider.create_security_group(name, description)
        return RedirectResponse("/security-groups", status_code=_REDIRECT)

    @app.post("/security-groups/{name}/delete")
    def secgroup_delete(name: str) -> Response:
        provider.delete_security_group(name)
        return RedirectResponse("/security-groups", status_code=_REDIRECT)

    # --- keypairs ---
    @app.post("/keypairs")
    def keypair_create(name: str = Form(...), public_key: str = Form(...)) -> Response:
        provider.create_keypair(name, public_key.strip())
        return RedirectResponse("/keypairs", status_code=_REDIRECT)

    @app.post("/keypairs/{name}/delete")
    def keypair_delete(name: str) -> Response:
        provider.delete_keypair(name)
        return RedirectResponse("/keypairs", status_code=_REDIRECT)

    # --- volume attach/detach ---
    @app.post("/volumes/{name}/attach")
    def volume_attach(name: str, server: str = Form(...)) -> Response:
        provider.attach_volume(name, server)
        return RedirectResponse("/volumes", status_code=_REDIRECT)

    @app.post("/volumes/{name}/detach")
    def volume_detach(name: str, server: str = Form(...)) -> Response:
        provider.detach_volume(name, server)
        return RedirectResponse("/volumes", status_code=_REDIRECT)

    return app


def serve(provider: CloudProvider, host: str = "127.0.0.1", port: int = 8787) -> None:
    """Run the web GUI with uvicorn (localhost by default).

    LG-02/LG-06: wire the federation logging stack once at this entry point
    before uvicorn starts so the GUI emits the same structured, redacted stream
    as the CLI (JSON off-TTY + a rotating file sink).
    """
    import uvicorn
    from mgf.common import bootstrap

    from mgf.cloud import __version__

    with bootstrap(app_name="mgf-cloud", app_version=__version__):
        LOG.info(
            "web.serve",
            extra={"operation": "web.serve", "host": host, "port": port},
        )
        uvicorn.run(create_app(provider), host=host, port=port)
