"""Thin FastAPI wrapper over `IngestService`.

FastAPI/pydantic are an OPTIONAL dependency (`pip install -e ".[http]"`) — importing the core library never
requires them; only `create_app` does. Endpoints:

  POST /v1/agent-events        ingest one agent tool-call event (Bearer token, X-Connector-Id)
  POST /v1/anchor              anchor the un-anchored journal tail (batch → root → attestation)
  GET  /v1/anchor-proof/{ch}   opaque_root + per-leaf inclusion proof for an anchored content_hash (#5)
  GET  /v1/anchor-status       host anchor mode; in dual mode: checkpoint_result / neo_result / overall,
                               separated (LIVE_NEO_ON_HOST_FLIP_PLAN §B.1 — checkpoint never masks NEO)
  GET  /healthz                liveness
"""
# NOTE: no `from __future__ import annotations` here — FastAPI must resolve the (locally-defined) Pydantic
# request models from real annotation objects; stringized annotations would make it mis-read the body as a query.

from typing import Optional

from cl.http.service import (
    AuthorityInput,
    IngestService,
    IngestServiceError,
)


def create_app(service: IngestService, *, title: str = "Cotrugli Ledger — Agent Ingest"):
    from fastapi import Depends, FastAPI, Header, HTTPException
    from pydantic import BaseModel, Field

    from cl.agent_seam.authority import AuthorityDecisionContentV1, AuthorityDecisionReceiptV1

    app = FastAPI(title=title)

    class AuthorityContentModel(BaseModel):
        action_intent_hash: str
        authority_context_manifest_hash: str
        decision: str
        policy_snapshot_ref: str
        issued_at: str
        valid_until: str
        signature_suite: str = "ed25519"
        authorized_effect_commitment: Optional[str] = None
        applied_limits: Optional[dict] = None
        required_cosigners: tuple = ()
        pac_bcvc_ref: Optional[str] = None

    class AuthorityReceiptModel(BaseModel):
        authority_decision_hash: str
        proof_purpose: str
        signer_ref: str
        signature: str

    class AuthorityModel(BaseModel):
        content: AuthorityContentModel
        receipt: AuthorityReceiptModel
        signer_key_ref: str

    class AgentEventRequest(BaseModel):
        connector_id: str
        event: dict
        action_intent_ref: Optional[str] = None
        authority: Optional[AuthorityModel] = None
        request_id: str = Field(default="req")
        received_at: str = Field(default="1970-01-01T00:00:00Z")

    def _bearer(authorization: Optional[str]) -> Optional[str]:
        if authorization is None:
            return None
        parts = authorization.split(" ", 1)
        return parts[1] if len(parts) == 2 and parts[0].lower() == "bearer" else authorization

    def _authority(model: Optional[AuthorityModel], hash_profile_id: bytes) -> Optional[AuthorityInput]:
        if model is None:
            return None
        content = AuthorityDecisionContentV1(
            action_intent_hash=model.content.action_intent_hash,
            authority_context_manifest_hash=model.content.authority_context_manifest_hash,
            decision=model.content.decision,
            policy_snapshot_ref=model.content.policy_snapshot_ref,
            issued_at=model.content.issued_at,
            valid_until=model.content.valid_until,
            hash_profile_id=hash_profile_id,
            signature_suite=model.content.signature_suite,
            authorized_effect_commitment=model.content.authorized_effect_commitment,
            applied_limits=model.content.applied_limits,
            required_cosigners=tuple(model.content.required_cosigners),
            pac_bcvc_ref=model.content.pac_bcvc_ref,
        )
        receipt = AuthorityDecisionReceiptV1(
            authority_decision_hash=model.receipt.authority_decision_hash,
            proof_purpose=model.receipt.proof_purpose,
            signer_ref=model.receipt.signer_ref,
            signature=model.receipt.signature,
        )
        return AuthorityInput(content=content, receipt=receipt, signer_key_ref=model.signer_key_ref)

    _STATUS = {
        "UNAUTHENTICATED": 401,
        "UNKNOWN_CONNECTOR": 404,
        "REJECTED": 422,
        "ANCHOR_FAILED": 502,
        "NOT_ANCHORED": 404,
        # index disagrees with the authoritative batch record — the
        # service refuses to answer rather than serve a false proof
        "PROOF_LOOKUP_CORRUPT": 500,
    }

    @app.get("/healthz")
    def healthz() -> dict:
        return {"status": "ok"}

    @app.get("/v1/anchor-status")
    def anchor_status() -> dict:
        """Host anchor-backend status. Dual/neo wrappers report through `host_anchor_status()`; a plain
        single backend reads as checkpoint mode. Never merges legs — reporting only, no evidence.
        Bounded-batch design v0.1: the un-anchored backlog depth and the per-round cap are always
        present, whatever the backend — the operator's back-pressure signal."""
        status_fn = getattr(service.anchor_backend, "host_anchor_status", None)
        if callable(status_fn):
            base = status_fn()
        else:
            base = {"mode": "checkpoint", "detail": "single anchor backend (no host wrapper active)"}
        return {
            "backlog_leaves": service.anchor_backlog(),
            "max_leaves_per_batch": service.max_anchor_leaves,
            **base,
        }

    @app.post("/v1/agent-events")
    def agent_events(req: AgentEventRequest, authorization: Optional[str] = Header(default=None)) -> dict:
        try:
            receipt = service.ingest_agent_event(
                token=_bearer(authorization),
                event=req.event,
                connector_id=req.connector_id,
                request_id=req.request_id,
                received_at=req.received_at,
                action_intent_ref=req.action_intent_ref,
                authority=_authority(req.authority, service.config.hash_profile_id),
            )
        except IngestServiceError as e:
            raise HTTPException(status_code=_STATUS.get(e.code, 400), detail={"code": e.code, "message": e.message})
        return receipt.__dict__

    @app.post("/v1/anchor")
    def anchor() -> dict:
        try:
            receipt = service.anchor_pending()
        except IngestServiceError as e:
            raise HTTPException(status_code=_STATUS.get(e.code, 400), detail={"code": e.code, "message": e.message})
        if receipt is None:
            return {"anchored": False, "reason": "nothing pending"}
        return {"anchored": True, **receipt.__dict__}

    @app.get("/v1/anchor-proof/{content_hash_hex}")
    def anchor_proof(content_hash_hex: str) -> dict:
        try:
            ch = bytes.fromhex(content_hash_hex)
        except ValueError:
            raise HTTPException(status_code=400, detail={"code": "BAD_REQUEST", "message": "content_hash must be hex"})
        try:
            view = service.anchor_proof(ch)
        except IngestServiceError as e:
            raise HTTPException(status_code=_STATUS.get(e.code, 400), detail={"code": e.code, "message": e.message})
        if view is None:
            raise HTTPException(
                status_code=404,
                detail={"code": "NOT_ANCHORED", "message": "no anchored batch contains this content_hash"},
            )
        return view.__dict__

    return app
