#!/usr/bin/python3
"""Upload one cdx result bundle to its attempt-bound controller mailbox."""
from __future__ import annotations

import hashlib
import http.client
import ipaddress
import json
import os
import re
import ssl
import sys
from pathlib import Path
from urllib.parse import urlsplit

MAX_BUNDLE_BYTES = 256 * 1024 * 1024
ATTEMPT_ID = re.compile(r"^[0-9a-f]{24}$")
TOKEN = re.compile(r"^[A-Za-z0-9_-]{43,128}$")


def main() -> int:
    data = json.loads(os.environ["CDX_RESULT_UPLOAD_JSON"])
    if not isinstance(data, dict) or set(data) != {"url", "token", "ca_pem"} or any(not isinstance(value, str) for value in data.values()):
        raise RuntimeError("invalid cdx result upload target")
    parsed = urlsplit(data["url"])
    address = ipaddress.ip_address(parsed.hostname or "")
    attempt = parsed.path.removeprefix("/v1/cdx-results/")
    if parsed.scheme != "https" or parsed.username or parsed.password or parsed.query or parsed.fragment or address.version != 4 or parsed.port is None or not ATTEMPT_ID.fullmatch(attempt) or not TOKEN.fullmatch(data["token"]):
        raise RuntimeError("invalid cdx result upload target")
    ca_pem = data["ca_pem"]
    if not ca_pem.startswith("-----BEGIN CERTIFICATE-----") or not ca_pem.rstrip().endswith("-----END CERTIFICATE-----"):
        raise RuntimeError("invalid cdx result upload certificate")
    bundle = Path(sys.argv[1] if len(sys.argv) == 2 else "/result/result.bundle")
    size = bundle.stat().st_size
    if not 0 < size <= MAX_BUNDLE_BYTES:
        raise RuntimeError("invalid cdx result bundle size")
    digest = hashlib.sha256()
    with bundle.open("rb") as source:
        for chunk in iter(lambda: source.read(1024 * 1024), b""):
            digest.update(chunk)
    context = ssl.create_default_context(cadata=ca_pem)
    connection = http.client.HTTPSConnection(parsed.hostname, parsed.port, timeout=120, context=context)
    try:
        connection.putrequest("PUT", parsed.path)
        connection.putheader("Authorization", f"Bearer {data['token']}")
        connection.putheader("Content-Type", "application/octet-stream")
        connection.putheader("Content-Length", str(size))
        connection.putheader("X-Content-SHA256", digest.hexdigest())
        connection.endheaders()
        with bundle.open("rb") as source:
            for chunk in iter(lambda: source.read(1024 * 1024), b""):
                connection.send(chunk)
        response = connection.getresponse()
        response.read(1024)
        if response.status != 201:
            raise RuntimeError(f"cdx result upload failed with HTTP {response.status}")
    finally:
        connection.close()
    return 0


if __name__ == "__main__":
    try:
        raise SystemExit(main())
    except Exception as exc:
        print(f"cdx-result-upload: {exc}", file=sys.stderr)
        raise SystemExit(1)
