#!/usr/bin/env python3
"""Local HTTP failure-injection exercise, Python standard library only.
No vendor APIs, real contacts, email sends or production systems are used.
Run: python3 replay.py > results.json
SQLite writes stand in for a downstream action, not email delivery.
Single-threaded receiver; not a production webhook implementation.
"""
import http.client
import json
import socket
import sqlite3
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer

EVENTS = [
    {"event": "E01", "contact": "C01", "fault": "none"},
    {"event": "E02", "contact": "C02", "fault": "503_before_write"},
    {"event": "E03", "contact": "C03", "fault": "disconnect_after_commit"},
    {"event": "E04", "contact": "C04", "fault": "redeliver_after_200"},
    {"event": "E05", "contact": "C01", "fault": "new_event_same_contact"},
]


def exercise(guarded):
    db = sqlite3.connect(":memory:", check_same_thread=False)
    db.execute("CREATE TABLE actions (program TEXT, event TEXT, step TEXT, contact TEXT)")
    if guarded:
        db.execute("CREATE UNIQUE INDEX action_identity ON actions(program, event, step)")
    attempts = {}
    trace = []

    class Receiver(BaseHTTPRequestHandler):
        def log_message(self, *_args):
            pass

        def do_POST(self):
            body = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
            event = body["event"]
            attempt = attempts.get(event, 0) + 1
            attempts[event] = attempt
            row = {"event": event, "attempt": attempt}
            if event == "E02" and attempt == 1:
                row.update(response="503", write="none")
                trace.append(row)
                self.send_response(503)
                self.end_headers()
                return
            try:
                with db:
                    db.execute("INSERT INTO actions VALUES (?, ?, ?, ?)",
                               ("resource-followup", event, "record-action", body["contact"]))
                row["write"] = "committed"
            except sqlite3.IntegrityError:
                row["write"] = "duplicate_suppressed"
            if event == "E03" and attempt == 1:
                row["response"] = "connection_closed_without_response"
                trace.append(row)
                self.close_connection = True
                self.connection.shutdown(socket.SHUT_RDWR)
                self.connection.close()
                return
            row["response"] = "200"
            trace.append(row)
            self.send_response(200)
            self.end_headers()
            self.wfile.write(b"accepted")

    server = HTTPServer(("127.0.0.1", 0), Receiver)
    thread = threading.Thread(target=server.serve_forever, daemon=True)
    thread.start()
    client_observations = []
    try:
        def post(event):
            connection = http.client.HTTPConnection("127.0.0.1", server.server_port, timeout=5)
            try:
                connection.request("POST", "/action", json.dumps(event), {"Content-Type": "application/json"})
                response = connection.getresponse()
                response.read()
                result = str(response.status)
            except http.client.RemoteDisconnected:
                result = "unknown_no_response"
            finally:
                connection.close()
            client_observations.append({"event": event["event"], "result": result})
            return result

        for event in EVENTS:
            status = post(event)
            if status in ("503", "unknown_no_response"):
                assert post(event) == "200"
            if event["fault"] == "redeliver_after_200":
                assert status == "200" and post(event) == "200"
    finally:
        server.shutdown()
        thread.join()
        server.server_close()
    counts = dict(db.execute("SELECT event, COUNT(*) FROM actions GROUP BY event ORDER BY event"))
    db.close()
    result = {"mode": "unique_action_key" if guarded else "append_every_request",
              "http_requests": sum(attempts.values()), "action_rows": sum(counts.values()),
              "duplicate_action_rows": sum(counts.values()) - len(EVENTS),
              "rows_by_event": counts, "client_observations": client_observations,
              "receiver_trace": trace}
    assert result["http_requests"] == 8
    assert result["action_rows"] == (5 if guarded else 7)
    assert counts["E01"] == counts["E05"] == 1
    return result


if __name__ == "__main__":
    print(json.dumps({"verified_date": "2026-10-21", "unique_events": 5,
                      "unique_contacts": 4, "events": EVENTS,
                      "limitations": "Local loopback HTTP and in-memory SQLite only; no email sending, vendor test, concurrency, crash recovery, retry timing or remote atomicity tested.",
                      "runs": [exercise(False), exercise(True)]}, indent=2) + "")
