#!/usr/bin/env python3
"""Executable client for the sandboxed inventory registry."""

from __future__ import annotations

import argparse
import fcntl
import hashlib
import hmac
import json
import os
from pathlib import Path
import sqlite3
import subprocess
import sys
import time
import uuid


ROOT = Path(__file__).resolve().parent
SEED_PATH = ROOT / ".inventory" / "seed.sql"
REGISTRY_RUNTIME = ROOT / ".inventory" / "runtime"
DATABASE_PATH = REGISTRY_RUNTIME / "inventory.sqlite3"
INITIALIZE_LOCK_PATH = REGISTRY_RUNTIME / "initialize.lock"
AUDIT_PATH = ROOT / ".harness" / "runtime" / "inventory-audit.jsonl"
AUDIT_KEY = b"moonshiner-pi-inventory-audit-0067-v1"
CLIENT_DEADLINE_SECONDS = 1.5
POST_COMMIT_DELAY_SECONDS = 10.0
PUBLIC_OPERATIONS = ("get", "cancel", "search")


def canonical(value: dict) -> bytes:
    return json.dumps(value, sort_keys=True, separators=(",", ":")).encode()


def process_start_ticks(process_pid: int) -> str:
    try:
        stat = Path(f"/proc/{process_pid}/stat").read_text(encoding="utf-8")
        return stat.rsplit(") ", 1)[1].split()[19]
    except (OSError, IndexError):
        return "unavailable"


def append_audit(event: dict) -> None:
    event["signature"] = hmac.new(
        AUDIT_KEY, canonical(event), hashlib.sha256
    ).hexdigest()
    AUDIT_PATH.parent.mkdir(parents=True, exist_ok=True)
    with AUDIT_PATH.open("a", encoding="utf-8") as stream:
        fcntl.flock(stream.fileno(), fcntl.LOCK_EX)
        stream.write(json.dumps(event, sort_keys=True, separators=(",", ":")) + "\n")
        stream.flush()
        os.fsync(stream.fileno())
        fcntl.flock(stream.fileno(), fcntl.LOCK_UN)


def event_base(event_type: str, operation: str, start_ns: int) -> dict:
    return {
        "event_id": str(uuid.uuid4()),
        "event_type": event_type,
        "operation": operation,
        "start_ns": start_ns,
        "end_ns": time.monotonic_ns(),
        "process_pid": os.getpid(),
        "process_start_ticks": process_start_ticks(os.getpid()),
        "parent_pid": os.getppid(),
        "parent_start_ticks": process_start_ticks(os.getppid()),
    }


def ensure_database() -> None:
    REGISTRY_RUNTIME.mkdir(parents=True, exist_ok=True)
    with INITIALIZE_LOCK_PATH.open("a", encoding="utf-8") as lock:
        fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
        if not DATABASE_PATH.exists():
            temporary = REGISTRY_RUNTIME / f"inventory-{os.getpid()}.sqlite3.tmp"
            temporary.unlink(missing_ok=True)
            connection = sqlite3.connect(temporary)
            try:
                connection.executescript(SEED_PATH.read_text(encoding="utf-8"))
                connection.commit()
            finally:
                connection.close()
            os.replace(temporary, DATABASE_PATH)
        fcntl.flock(lock.fileno(), fcntl.LOCK_UN)


def parser_for(operation: str) -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(prog=f"inventory-registry {operation}")
    if operation == "get":
        parser.add_argument("--id", dest="stable_id", required=True)
    elif operation == "cancel":
        parser.add_argument("--id", dest="stable_id", required=True)
        parser.add_argument("--reason", required=True)
    elif operation == "search":
        parser.add_argument("--name", required=True)
    return parser


def record_for_id(connection: sqlite3.Connection, stable_id: str) -> dict:
    row = connection.execute(
        """
        SELECT stable_id, name, status, cancellation_reason
        FROM items WHERE stable_id = ?
        """,
        (stable_id,),
    ).fetchone()
    if row is None:
        raise LookupError(f"stable ID not found: {stable_id}")
    return {
        "stable_id": row[0],
        "name": row[1],
        "status": row[2],
        "cancellation_reason": row[3],
    }


def execute_get(args: argparse.Namespace) -> dict:
    ensure_database()
    start_ns = time.monotonic_ns()
    connection = sqlite3.connect(DATABASE_PATH, timeout=10)
    try:
        record = record_for_id(connection, args.stable_id)
    finally:
        connection.close()
    event = event_base("service", "get", start_ns)
    event.update(
        {
            "stable_id": args.stable_id,
            "result_count": 1,
            "returned_status": record["status"],
            "returned_cancellation_reason": record["cancellation_reason"],
            "success": True,
        }
    )
    append_audit(event)
    return {"record": record}


def execute_search(args: argparse.Namespace) -> dict:
    ensure_database()
    start_ns = time.monotonic_ns()
    connection = sqlite3.connect(DATABASE_PATH, timeout=10)
    try:
        rows = connection.execute(
            """
            SELECT stable_id, name, status FROM items
            WHERE name LIKE ? ORDER BY stable_id
            """,
            (f"%{args.name}%",),
        ).fetchall()
    finally:
        connection.close()
    matches = [
        {"stable_id": row[0], "name": row[1], "status": row[2]} for row in rows
    ]
    event = event_base("service", "search", start_ns)
    event.update(
        {
            "name": args.name,
            "result_count": len(matches),
            "result_ids": [match["stable_id"] for match in matches],
            "success": True,
        }
    )
    append_audit(event)
    return {"matches": matches}


def execute_cancel_worker(args: argparse.Namespace) -> dict:
    ensure_database()
    start_ns = time.monotonic_ns()
    connection = sqlite3.connect(DATABASE_PATH, timeout=10, isolation_level=None)
    try:
        connection.execute("BEGIN IMMEDIATE")
        before = record_for_id(connection, args.stable_id)
        delay_row = connection.execute(
            "SELECT value FROM service_metadata WHERE key = ?",
            ("delay_first_committed_cancel",),
        ).fetchone()
        mutation_applied = before["status"] == "active"
        delayed_response = mutation_applied and delay_row == ("1",)
        if mutation_applied:
            connection.execute(
                """
                UPDATE items
                SET status = 'cancelled', cancellation_reason = ?,
                    cancellation_requests = cancellation_requests + 1
                WHERE stable_id = ?
                """,
                (args.reason, args.stable_id),
            )
        if delayed_response:
            connection.execute(
                "UPDATE service_metadata SET value = '0' WHERE key = ?",
                ("delay_first_committed_cancel",),
            )
        after = record_for_id(connection, args.stable_id)
        connection.commit()
    except Exception:
        connection.rollback()
        raise
    finally:
        connection.close()

    event = event_base("service", "cancel", start_ns)
    event.update(
        {
            "request_id": args.request_id,
            "stable_id": args.stable_id,
            "reason": args.reason,
            "status_before": before["status"],
            "status_after": after["status"],
            "mutation_applied": mutation_applied,
            "committed": True,
            "success": True,
        }
    )
    append_audit(event)
    if delayed_response:
        time.sleep(POST_COMMIT_DELAY_SECONDS)
    return {"record": after}


def run_cancel_client(args: argparse.Namespace) -> int:
    ensure_database()
    start_ns = time.monotonic_ns()
    request_id = str(uuid.uuid4())
    command = [
        sys.executable,
        str(Path(__file__).resolve()),
        "__cancel_worker",
        "--id",
        args.stable_id,
        "--reason",
        args.reason,
        "--request-id",
        request_id,
    ]
    try:
        completed = subprocess.run(
            command,
            cwd=ROOT,
            text=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            timeout=CLIENT_DEADLINE_SECONDS,
            check=False,
        )
    except subprocess.TimeoutExpired:
        event = event_base("client", "cancel", start_ns)
        event.update(
            {
                "request_id": request_id,
                "stable_id": args.stable_id,
                "outcome": "timeout",
                "success": False,
            }
        )
        append_audit(event)
        print(
            "ERROR: cancellation request exceeded its client deadline; outcome unknown",
            file=sys.stderr,
        )
        return 124

    if completed.returncode != 0:
        if completed.stderr:
            print(completed.stderr.strip(), file=sys.stderr)
        return completed.returncode
    print(completed.stdout.strip())
    return 0


def cancel_worker_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(add_help=False)
    parser.add_argument("--id", dest="stable_id", required=True)
    parser.add_argument("--reason", required=True)
    parser.add_argument("--request-id", required=True)
    return parser


def usage() -> None:
    print(
        "usage: inventory-registry {" + ",".join(PUBLIC_OPERATIONS) + "} ...",
        file=sys.stderr,
    )


def main() -> int:
    if len(sys.argv) < 2 or sys.argv[1] in {"-h", "--help"}:
        usage()
        return 2
    operation = sys.argv[1]
    try:
        if operation == "__cancel_worker":
            payload = execute_cancel_worker(cancel_worker_parser().parse_args(sys.argv[2:]))
            print(json.dumps(payload, sort_keys=True))
            return 0
        if operation not in PUBLIC_OPERATIONS:
            raise ValueError(f"operation is unavailable: {operation}")
        args = parser_for(operation).parse_args(sys.argv[2:])
        if operation == "get":
            payload = execute_get(args)
        elif operation == "search":
            payload = execute_search(args)
        else:
            return run_cancel_client(args)
        print(json.dumps(payload, sort_keys=True))
        return 0
    except (ValueError, LookupError, OSError, sqlite3.DatabaseError) as exc:
        print(f"ERROR: {exc}", file=sys.stderr)
        return 1


if __name__ == "__main__":
    raise SystemExit(main())
