"""Authenticated local control API.

Arbitrary egress is disabled unless the operator explicitly enables it.
"""

from __future__ import annotations

import base64
import hashlib
import hmac
from typing import Any

from fastapi import Depends, FastAPI, Header, HTTPException
from fastapi.responses import JSONResponse
from pydantic import BaseModel, ConfigDict, Field

from .engine import ScopeWritEngine
from .errors import ScopeWritError
from .gateway import GuardedHTTPClient
from .models import ActionProposal, SignedEnvelope


class StartRunRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")
    manifest: SignedEnvelope


class AuthorizeRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")
    attestation: str
    proposal: ActionProposal
    approval: str | None = None


class HTTPRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")
    attestation: str
    run_id: str
    request_id: str
    method: str
    url: str
    justification: str | None = None
    approval: str | None = None
    headers: dict[str, str] = Field(default_factory=dict)
    content_b64: str | None = Field(default=None, max_length=400_000)


def api_key_sha256(api_key: str) -> str:
    return hashlib.sha256(api_key.encode("utf-8")).hexdigest()


def create_app(
    engine: ScopeWritEngine,
    *,
    expected_api_key_sha256: str,
    enable_http_egress: bool = False,
) -> FastAPI:
    if len(expected_api_key_sha256) != 64 or any(
        character not in "0123456789abcdef" for character in expected_api_key_sha256
    ):
        raise ValueError("expected_api_key_sha256 must be a SHA-256 hex digest")

    def require_api_key(
        x_scopewrit_key: str | None = Header(default=None),
    ) -> None:
        if x_scopewrit_key is None:
            raise HTTPException(status_code=401, detail="missing API key")
        observed = api_key_sha256(x_scopewrit_key)
        if not hmac.compare_digest(observed, expected_api_key_sha256):
            raise HTTPException(status_code=401, detail="invalid API key")

    app = FastAPI(
        title="ScopeWrit",
        version="0.1.0",
        description="Signed, fail-closed run authority for autonomous agents.",
    )

    @app.exception_handler(ScopeWritError)
    async def handle_scopewrit_error(_, exc: ScopeWritError) -> JSONResponse:
        return JSONResponse(
            status_code=403,
            content={
                "error": type(exc).__name__,
                "detail": str(exc),
            },
        )

    @app.get("/api/health")
    def health() -> dict[str, Any]:
        return {
            "status": "ok",
            "product": "ScopeWrit",
            "version": "0.1.0",
            "http_egress_enabled": enable_http_egress,
        }

    @app.post("/v1/runs/start", dependencies=[Depends(require_api_key)])
    def start_run(request: StartRunRequest) -> dict[str, Any]:
        return engine.start_run(request.manifest).model_dump(mode="json")

    @app.post("/v1/authorize", dependencies=[Depends(require_api_key)])
    def authorize(request: AuthorizeRequest) -> dict[str, Any]:
        return engine.authorize(
            attestation=request.attestation,
            proposal=request.proposal,
            approval=request.approval,
        ).model_dump(mode="json")

    @app.get("/v1/runs/{run_id}/audit", dependencies=[Depends(require_api_key)])
    def audit_packet(run_id: str) -> dict[str, Any]:
        return engine.read_audit_packet(run_id).model_dump(mode="json")

    @app.post(
        "/v1/runs/{run_id}/close",
        dependencies=[Depends(require_api_key)],
    )
    def close_run(run_id: str) -> dict[str, Any]:
        return engine.close_run(run_id).model_dump(mode="json")

    @app.post("/v1/egress/http", dependencies=[Depends(require_api_key)])
    def guarded_http(request: HTTPRequest) -> dict[str, Any]:
        if not enable_http_egress:
            raise HTTPException(
                status_code=403,
                detail="HTTP egress adapter is disabled by the operator",
            )
        content = None
        if request.content_b64 is not None:
            try:
                content = base64.b64decode(request.content_b64, validate=True)
            except ValueError as exc:
                raise HTTPException(
                    status_code=422,
                    detail="content_b64 is invalid",
                ) from exc
            if len(content) > 256 * 1024:
                raise HTTPException(
                    status_code=413,
                    detail="request content exceeds 256 KiB",
                )
        with GuardedHTTPClient(engine) as client:
            result = client.request(
                attestation=request.attestation,
                run_id=request.run_id,
                request_id=request.request_id,
                method=request.method,
                url=request.url,
                justification=request.justification,
                headers=request.headers,
                content=content,
                approval=request.approval,
            )
        response_body = result.value.content
        if len(response_body) > 1024 * 1024:
            raise HTTPException(
                status_code=502,
                detail="upstream response exceeds 1 MiB",
            )
        return {
            "decision": result.decision.model_dump(mode="json"),
            "upstream": {
                "status_code": result.value.status_code,
                "headers": {
                    key: value
                    for key, value in result.value.headers.items()
                    if key.lower() in {"content-type", "content-length", "location"}
                },
                "body_b64": base64.b64encode(response_body).decode("ascii"),
            },
        }

    return app
