"""Minimal stdlib HTTP POST helper.

post_json(url, body, timeout) -> (status_code, parsed_or_None)

    POSTs `body` (a dict) as JSON to `url`. Fresh connection per call (no pooling).
    Returns (status_code, parsed_json) on a parseable response.
    Returns (status_code, None) when the response body is not valid JSON.
    Raises TransportError on connection failure, timeout, or transport-level error.

Constants:
    SIGNALS_PATH = "/signals"
    ERRORS_PATH  = "/errors"

No third-party dependencies.

Note: the intake route is mounted at /signals. SIGNALS_PATH is already correct.
"""

from __future__ import annotations

import json
import urllib.error
import urllib.request
from typing import Any


SIGNALS_PATH = "/signals"
ERRORS_PATH = "/errors"


class TransportError(Exception):
    """Raised on connection failure, timeout, or non-JSON transport body."""


class EgressBlocked(RuntimeError):
    """Raised when something tries to send data out during a validation run."""


# Set once, by the preflight entry, before any author code runs.
#
# Validation collects what a scanner produces and reports it; delivery is the runtime's job after
# installation. Nothing is configured to receive a post during a validation run, so in practice
# there is nowhere for one to go — but "nothing is configured" is a property of how the caller was
# set up, provable only by tracing several layers, and one refactor away from silently ceasing to
# be true.
#
# What this is NOT: a security boundary. Author code runs in this process and can set the flag back,
# reach past the client wrapper, or build its own client from the environment — all reproducible in
# a single scan(). It is deliberately not hardened against that, because hardening here would buy a
# false sense of one: the invariants that actually hold live at the MCP and engine layers, where
# they hold no matter what a scanner does.
#
# What it IS: the ordinary path, closed by default, failing loudly instead of silently delivering
# during a check. That is worth having on its own terms.
PREFLIGHT = False


def post_json(url: str, body: dict, timeout: float) -> tuple[int, Any]:
    """POST body as JSON to url.

    Returns (status_code, parsed_json_or_None).
    Raises TransportError on connection/timeout failures.
    Raises EgressBlocked when a validation run is in progress.
    """
    if PREFLIGHT:
        raise EgressBlocked(
            "refusing to POST to {}: validation collects and reports signals, it never "
            "delivers them".format(url)
        )

    data = json.dumps(body).encode("utf-8")
    req = urllib.request.Request(
        url,
        data=data,
        headers={
            "Content-Type": "application/json",
            "Content-Length": str(len(data)),
        },
        method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            status = resp.status
            raw = resp.read()
    except urllib.error.HTTPError as exc:
        # HTTPError has a status and a response body
        status = exc.code
        try:
            raw = exc.read()
        except Exception:
            raw = b""
    except (urllib.error.URLError, OSError, TimeoutError) as exc:
        raise TransportError(str(exc)) from exc

    try:
        parsed = json.loads(raw.decode("utf-8")) if raw else None
    except (json.JSONDecodeError, UnicodeDecodeError):
        parsed = None

    return (status, parsed)
