"""Scoped security halt example. Python 3.12+, standard library, POSIX local storage."""

import argparse
from contextlib import closing
import getpass
import http.client
import json
import os
from pathlib import Path
import sqlite3
import ssl
import stat
import sys
import time
import unicodedata
from urllib.parse import urlsplit
from uuid import UUID


STATES = {"accepted", "enforcing", "enforced", "partially_enforced", "failed", "released"}
WORK_STATES = {"pending", "cancellation_requested", "cancelled", "terminal", "unknown", "failed"}
RETRY_STATUSES = {500, 502, 503, 504}
MAX_RESPONSE_BYTES = 512 * 1024


class HandlerError(RuntimeError):
    def __init__(self, code, *, status=None, error_id=None):
        super().__init__(code)
        self.code, self.status, self.error_id = code, status, error_id


class RetryableFailure(HandlerError):
    pass


def uuid_string(value):
    if not isinstance(value, str):
        raise HandlerError("invalid_uuid")
    try:
        return str(UUID(value))
    except ValueError as exc:
        raise HandlerError("invalid_uuid") from exc


def normalized_text(value, maximum, *, required=True):
    if not isinstance(value, str):
        raise HandlerError("invalid_incident_text")
    value = unicodedata.normalize("NFC", value).strip()
    if (required and not value) or len(value) > maximum:
        raise HandlerError("invalid_incident_text")
    try:
        value.encode("utf-8")
    except UnicodeEncodeError as exc:
        raise HandlerError("invalid_incident_text") from exc
    return value


def approved_request(incident, asset_map):
    required = {"event_id", "asset_id", "reason"}
    if (not isinstance(incident, dict) or not required <= incident.keys()
            or incident.keys() - required - {"incident_reference"}):
        raise HandlerError("invalid_incident_fields")
    asset_id = normalized_text(incident["asset_id"], 255)
    if not isinstance(asset_map, dict) or asset_id not in asset_map:
        raise HandlerError("asset_not_approved")
    return {
        "external_event_id": normalized_text(incident["event_id"], 160),
        "target": {"type": "agent", "id": uuid_string(asset_map[asset_id])},
        "action": "halt",
        "reason": normalized_text(incident["reason"], 1000),
        "incident_reference": normalized_text(incident.get("incident_reference", ""), 255, required=False),
    }


def https_origin(value):
    try:
        parsed = urlsplit(value)
        port = parsed.port
        if (any(char.isspace() or ord(char) < 32 or ord(char) == 127 for char in value) or port == 0
                or parsed.scheme != "https" or not parsed.hostname or parsed.username is not None
                or parsed.password is not None or parsed.path not in ("", "/")
                or parsed.query or parsed.fragment):
            raise ValueError()
    except ValueError as exc:
        raise HandlerError("invalid_https_origin") from exc
    host = parsed.hostname
    authority = f"[{host}]" if ":" in host else host
    return f"https://{authority}" + (f":{port}" if port and port != 443 else "")


class Store:
    def __init__(self, directory, origin, identity_id):
        if os.name != "posix":
            raise HandlerError("posix_storage_required")
        directory = Path(directory)
        directory.mkdir(mode=0o700, parents=True, exist_ok=True)
        metadata = directory.stat()
        if (directory.is_symlink() or metadata.st_uid != os.getuid()
                or stat.S_IMODE(metadata.st_mode) & 0o077):
            raise HandlerError("state_directory_must_be_private")
        database = directory / "incidents.sqlite3"
        descriptor = os.open(database, os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW, 0o600)
        try:
            metadata = os.fstat(descriptor)
            if metadata.st_uid != os.getuid() or stat.S_IMODE(metadata.st_mode) & 0o077:
                raise HandlerError("state_database_must_be_private")
        finally:
            os.close(descriptor)
        self.db = sqlite3.connect(database, timeout=10)
        try:
            with self.db:
                self.db.execute("PRAGMA synchronous=FULL")
                self.db.execute("""CREATE TABLE IF NOT EXISTS binding (
                    slot INTEGER PRIMARY KEY CHECK (slot = 1), origin TEXT, identity_id TEXT)""")
                self.db.execute("""CREATE TABLE IF NOT EXISTS incidents (
                    event_id TEXT PRIMARY KEY, request TEXT NOT NULL,
                    intervention_id TEXT, response TEXT, control_state TEXT)""")
                self.db.execute("INSERT OR IGNORE INTO binding VALUES (1, ?, ?)", (origin, identity_id))
                binding = self.db.execute("SELECT origin, identity_id FROM binding WHERE slot = 1").fetchone()
                if binding != (origin, identity_id):
                    raise HandlerError("state_binding_conflict")
        except (sqlite3.Error, HandlerError):
            self.db.close()
            raise

    def close(self):
        self.db.close()

    def prepare(self, payload):
        serialized = json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
        if len(serialized.encode("utf-8")) > 16 * 1024:
            raise HandlerError("request_too_large")
        event_id = payload["external_event_id"]
        # Commit immutable semantics before any I/O, including the first ambiguous POST.
        with self.db:
            self.db.execute("INSERT OR IGNORE INTO incidents (event_id, request) VALUES (?, ?)",
                            (event_id, serialized))
            request, intervention_id = self.db.execute(
                "SELECT request, intervention_id FROM incidents WHERE event_id = ?", (event_id,),
            ).fetchone()
            if request != serialized:
                raise HandlerError("local_event_conflict")
        return request, intervention_id

    def save_intervention(self, event_id, response):
        with self.db:
            previous = self.db.execute(
                "SELECT intervention_id FROM incidents WHERE event_id = ?", (event_id,),
            ).fetchone()[0]
            if previous is not None and previous != response["id"]:
                raise HandlerError("intervention_id_changed")
            self.db.execute("UPDATE incidents SET intervention_id = ?, response = ? WHERE event_id = ?",
                            (response["id"], json.dumps(response), event_id))

    def save_control(self, event_id, response):
        with self.db:
            self.db.execute("UPDATE incidents SET control_state = ? WHERE event_id = ?",
                            (json.dumps(response), event_id))


class Client:
    def __init__(self, origin, token):
        self.origin = https_origin(origin)
        self.address = urlsplit(self.origin)
        if not token or not token.isascii() or any(ord(char) <= 32 or ord(char) == 127 for char in token):
            raise HandlerError("invalid_bearer_token")
        self.token = token

    def _request(self, method, path, body, timeout):
        connection = http.client.HTTPSConnection(self.address.hostname, self.address.port, timeout=timeout,
                                                 context=ssl.create_default_context())
        try:
            connection.request(method, path, body=body.encode("utf-8") if body else None, headers={
                "Authorization": f"Bearer {self.token}", "Accept": "application/json",
                "Content-Type": "application/json",
            })
            response = connection.getresponse()
            content = response.read(MAX_RESPONSE_BYTES + 1)
            if len(content) > MAX_RESPONSE_BYTES:
                raise HandlerError("response_too_large")
            try:
                result = json.loads(content)
            except (json.JSONDecodeError, UnicodeDecodeError):
                result = None
            error_id = result.get("error_id") if isinstance(result, dict) else None
            if error_id is not None:
                try:
                    error_id = uuid_string(error_id)
                except HandlerError:
                    error_id = None
            if response.status in RETRY_STATUSES:
                raise RetryableFailure("server_result_uncertain", status=response.status, error_id=error_id)
            expected = {200, 202} if method == "POST" else {200}
            if response.status not in expected:
                code = {400: "invalid_request", 401: "authentication_failed", 403: "permission_denied",
                        404: "target_or_intervention_not_found", 409: "request_conflict",
                        413: "request_too_large"}.get(response.status, "unexpected_http_status")
                if (response.status == 409 and isinstance(result, dict)
                        and result.get("error") == "external_event_id_conflict"):
                    code = "external_event_id_conflict"
                raise HandlerError(code, status=response.status, error_id=error_id)
            if not isinstance(result, dict):
                raise RetryableFailure("invalid_json_response")
            return response.status, result
        except ssl.SSLCertVerificationError as exc:
            raise HandlerError("tls_certificate_verification_failed") from exc
        except (OSError, http.client.HTTPException) as exc:
            raise RetryableFailure("transport_result_uncertain") from exc
        finally:
            connection.close()

    def request(self, method, path, body=None, *, deadline=None):
        for attempt in range(3):
            remaining = deadline - time.monotonic() if deadline is not None else 10
            if remaining <= 0:
                raise HandlerError("polling_budget_exhausted")
            try:
                return self._request(method, path, body, min(10, remaining))
            except RetryableFailure:
                if attempt == 2:
                    raise
                delay = min(2 ** attempt, max(0, deadline - time.monotonic())) if deadline is not None else 2 ** attempt
                time.sleep(delay)


def validate_intervention(response, payload, *, status, method, intervention_id=None):
    expected_replay = method == "POST" and status == 200
    if (response.get("target") != payload["target"] or response.get("action") != "halt"
            or response.get("external_event_id") != payload["external_event_id"]
            or response.get("reason") != payload["reason"]
            or response.get("incident_reference") != payload["incident_reference"]
            or response.get("state") not in tuple(STATES) or type(response.get("hold_active")) is not bool
            or response.get("replayed") is not expected_replay):
        raise HandlerError("invalid_intervention_response")
    current_id = uuid_string(response.get("id"))
    if intervention_id is not None and current_id != intervention_id:
        raise HandlerError("intervention_id_changed")
    if (response.get("status_url") != f"/api/v1/security/interventions/{current_id}"
            or response.get("control_state_url") != f"/api/v1/security/targets/agent/{payload['target']['id']}/control-state"):
        raise HandlerError("invalid_response_paths")
    enforcement = response.get("enforcement")
    if (not isinstance(enforcement, dict)
            or enforcement.get("admission_barrier") not in ("active", "inactive")
            or enforcement.get("dispatch_barrier") not in ("active", "inactive")
            or enforcement.get("worker_acknowledgement") not in ("pending", "acknowledged", "failed")):
        raise HandlerError("invalid_enforcement_evidence")
    validate_counts(enforcement.get("known_work"), WORK_STATES)


def validate_counts(counts, names):
    if (not isinstance(counts, dict) or not names <= counts.keys()
            or any(type(counts[name]) is not int or counts[name] < 0 for name in names)):
        raise HandlerError("invalid_work_counts")


def handle_incident(client, store, payload, *, poll_seconds=120, max_reads=12):
    body, intervention_id = store.prepare(payload)
    if intervention_id is None:
        status, response = client.request("POST", "/api/v1/security/interventions", body)
        validate_intervention(response, payload, status=status, method="POST")
        store.save_intervention(payload["external_event_id"], response)
        intervention_id = response["id"]
    status_path = f"/api/v1/security/interventions/{intervention_id}"
    control_path = f"/api/v1/security/targets/agent/{payload['target']['id']}/control-state"
    deadline = time.monotonic() + poll_seconds
    for read_number in range(max_reads):
        status, response = client.request("GET", status_path, deadline=deadline)
        validate_intervention(response, payload, status=status, method="GET", intervention_id=intervention_id)
        store.save_intervention(payload["external_event_id"], response)
        if response["state"] not in {"accepted", "enforcing"}:
            break
        remaining = deadline - time.monotonic()
        if remaining <= 0 or read_number == max_reads - 1:
            break
        time.sleep(min(2 ** min(read_number, 3), remaining))
        if time.monotonic() >= deadline:
            break
    _, control = client.request("GET", control_path)
    workers, barriers = control.get("workers"), control.get("barriers")
    if (control.get("target") != payload["target"] or type(control.get("held")) is not bool
            or control.get("effective_state") not in tuple((STATES - {"released"}) | {"active"})
            or not isinstance(control.get("active_intervention_ids"), list)
            or not isinstance(workers, dict) or type(workers.get("acknowledged")) is not bool
            or workers.get("status") not in ("not_applicable", "pending", "verified", "failed")
            or not isinstance(barriers, dict)
            or any(barriers.get(name) not in ("active", "inactive") for name in ("admission", "dispatch"))):
        raise HandlerError("invalid_control_state")
    for active_id in control["active_intervention_ids"]:
        uuid_string(active_id)
    validate_counts(control.get("in_flight"), (WORK_STATES - {"pending"}) | {"known"})
    store.save_control(payload["external_event_id"], control)
    unresolved = sum(control["in_flight"][name] for name in ("known", "cancellation_requested", "unknown", "failed"))
    enforced = (response["state"] == "enforced" and response["hold_active"] and control["held"]
                and control["effective_state"] == "enforced" and intervention_id in control["active_intervention_ids"]
                and workers == {"acknowledged": True, "status": "verified"}
                and barriers == {"admission": "active", "dispatch": "active"} and unresolved == 0)
    return {
        "outcome": "enforced_hold_active" if enforced else "human_review_required",
        "intervention_id": intervention_id, "state": response["state"], "hold_active": response["hold_active"],
        "enforcement": response["enforcement"], "control_state": control,
        "next_action": "Review the incident in DT; release only through authorized human recovery.",
    }


def main(argv=None):
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--origin", required=True, help="HTTPS deployment origin, without a path")
    parser.add_argument("--identity-id", required=True, help="Stable service identity UUID; rotation retains it")
    parser.add_argument("--asset-map", required=True, type=Path)
    parser.add_argument("--incident", required=True, type=Path)
    parser.add_argument("--state-dir", required=True, type=Path)
    parser.add_argument("--poll-seconds", type=int, default=120, choices=range(1, 3601), metavar="1..3600")
    args = parser.parse_args(argv)
    try:
        origin, identity_id = https_origin(args.origin), uuid_string(args.identity_id)
        payload = approved_request(json.loads(args.incident.read_text()), json.loads(args.asset_map.read_text()))
        token = os.environ.get("DT_SECURITY_TOKEN") or getpass.getpass("Security service bearer token: ")
        client = Client(origin, token)
        with closing(Store(args.state_dir, origin, identity_id)) as store:
            result = handle_incident(client, store, payload, poll_seconds=args.poll_seconds)
        print(json.dumps(result, indent=2))
        return 0 if result["outcome"] == "enforced_hold_active" else 2
    except HandlerError as exc:
        print(json.dumps({"outcome": "human_review_required", "error": exc.code,
                          "http_status": exc.status, "error_id": exc.error_id,
                          "next_action": "Keep the saved request and event ID. Inspect local state and DT; a hold may exist."}),
              file=sys.stderr)
        return 1
    except (OSError, sqlite3.Error, json.JSONDecodeError, UnicodeDecodeError, EOFError):
        print('{"outcome":"human_review_required","error":"local_input_or_storage_error",'
              '"next_action":"Retain existing state; recover storage or input before retrying the same event."}',
              file=sys.stderr)
        return 1


if __name__ == "__main__":
    sys.exit(main())
