"""POSIX local start/readiness/catalog/stop demonstration; controls only its own child."""
import argparse
import hashlib
import json
from pathlib import Path
import selectors
import socket
import subprocess
import sys
import time
import urllib.error
import urllib.request

ROOT = Path(__file__).resolve().parents[1]
parser = argparse.ArgumentParser()
parser.add_argument("--timeout", type=float, default=3)
args = parser.parse_args()
if not 0 < args.timeout <= 10:
    parser.error("timeout must be positive and at most 10 seconds")
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
proc = subprocess.Popen([sys.executable, "-u", "scripts/serve_catalog.py", "--port", "0"],
                        cwd=ROOT, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
result = {"status": "failed", "pid": proc.pid}
port = None
try:
    with selectors.DefaultSelector() as ready:
        ready.register(proc.stdout, selectors.EVENT_READ)
        if not ready.select(args.timeout):
            raise RuntimeError("no listening announcement before timeout")
        address = json.loads(proc.stdout.readline())
    if address.get("host") != "127.0.0.1" or address.get("event") != "listening":
        raise RuntimeError("unexpected listening announcement")
    port = int(address["port"])
    result["address"] = address
    deadline = time.monotonic() + args.timeout
    while True:
        try:
            with opener.open("http://127.0.0.1:%d/health" % port, timeout=0.5) as response:
                body = response.read()
                result["health"] = {"status": response.status, "body": body.decode()}
            if body != b"ok":
                raise RuntimeError("unexpected health body")
            break
        except urllib.error.HTTPError as error:
            result["health"] = {"status": error.code, "body": error.read().decode()}
        except (urllib.error.URLError, TimeoutError):
            pass
        if time.monotonic() >= deadline:
            raise RuntimeError("service not ready before timeout")
        time.sleep(0.05)
    with opener.open("http://127.0.0.1:%d/catalog" % port, timeout=1) as response:
        catalog = response.read()
        if response.status != 200 or catalog != (ROOT / "build/catalog.json").read_bytes():
            raise RuntimeError("served catalog differs from checked artifact")
        result["catalog"] = {"status": response.status, "bytes": len(catalog),
                             "body": catalog.decode(), "sha256": hashlib.sha256(catalog).hexdigest()}
    result["status"] = "ok"
except (OSError, ValueError, RuntimeError) as error:
    result["error"] = str(error)
finally:
    if proc.poll() is None:
        proc.terminate()
    try:
        proc.wait(timeout=3)
    except subprocess.TimeoutExpired:
        proc.kill()
        proc.wait(timeout=3)
    result["serverExit"] = proc.returncode
    result["childStopped"] = proc.poll() is not None
    if port is not None:
        with socket.socket() as probe:
            probe.settimeout(0.3)
            result["portClosedAfterStop"] = probe.connect_ex(("127.0.0.1", port)) != 0
    result["serverStderr"] = proc.stderr.read()
    proc.stdout.close()
    proc.stderr.close()
print(json.dumps(result, sort_keys=True))
raise SystemExit(0 if result["status"] == "ok" else 1)
