import json
import os
from urllib.parse import urlsplit

from fastapi import FastAPI, Request, Response
from stormedo import AsyncDeliveryVerifier, DeliveryVerificationError


def create_app(*, verifier=None):
    project_id = os.environ["STORMEDO_PROJECT_ID"]
    public_url = os.environ["PUBLIC_WEBHOOK_URL"]
    destination = urlsplit(public_url)
    if destination.scheme != "https" or not destination.hostname or destination.username or destination.password or destination.fragment:
        raise ValueError("PUBLIC_WEBHOOK_URL must be the public HTTPS destination URL.")
    verifier = verifier or AsyncDeliveryVerifier(project_id)

    app = FastAPI(redirect_slashes=False)

    @app.post(destination.path or "/")
    async def receive(request: Request):
        def single_header(name):
            values = [value for key, value in request.scope["headers"] if key.lower() == name]
            return values[0].decode("latin-1") if len(values) == 1 and values[0] else None

        signature = single_header(b"stormedo-signature")
        request_id = single_header(b"stormedo-request-id")
        if not signature or not request_id:
            return Response(status_code=400)
        if request.headers.get("content-encoding", "identity") != "identity":
            return Response(status_code=415)
        raw_body = bytearray()
        async for chunk in request.stream():
            if len(raw_body) + len(chunk) > 256 * 1024:
                return Response(status_code=413)
            raw_body.extend(chunk)
        try:
            delivery = await verifier.verify_delivery(
                signature=signature, request_id=request_id,
                expected_url=public_url, method=request.method, raw_body=raw_body,
            )
        except DeliveryVerificationError as error:
            print("Delivery verification failed:", error.code)
            return Response(status_code=503 if error.code == "jwks_unavailable" else 401)
        try:
            event = json.loads(raw_body)
        except (ValueError, UnicodeDecodeError):
            return Response(status_code=400)
        print("Received event:", delivery.request_id, event)
        return Response(status_code=204)

    return app
