#!/usr/bin/env python3
"""Executable interface to the sandboxed inventory record ledger."""

from __future__ import annotations

import argparse
import fcntl
import hashlib
import hmac
import json
import os
from pathlib import Path
import sys
import tempfile
import time
from typing import Any
import uuid


ROOT = Path(__file__).resolve().parent
STATE_DIR = ROOT / ".inventory"
RECORDS_PATH = STATE_DIR / "records.json"
AUDIT_PATH = STATE_DIR / "audit.jsonl"
NOTIFICATIONS_PATH = STATE_DIR / "notifications.json"
LOCK_PATH = STATE_DIR / "lock"
KEY_PATH = ROOT / ".harness" / "audit.key"
OPERATION_DELAY_SECONDS = 0.55
OPERATIONS = ("get", "update", "search", "cancel", "create", "notify")


def canonical(value: Any) -> bytes:
    return json.dumps(
        value, ensure_ascii=False, sort_keys=True, separators=(",", ":")
    ).encode("utf-8")


def load_json(path: Path) -> dict[str, Any]:
    with path.open(encoding="utf-8") as stream:
        value = json.load(stream)
    if not isinstance(value, dict):
        raise RuntimeError(f"invalid ledger file: {path.name}")
    return value


def atomic_json_write(path: Path, value: dict[str, Any]) -> None:
    descriptor, temporary_name = tempfile.mkstemp(
        dir=path.parent, prefix=f".{path.name}.", text=True
    )
    temporary = Path(temporary_name)
    try:
        with os.fdopen(descriptor, "w", encoding="utf-8") as stream:
            json.dump(value, stream, ensure_ascii=False, indent=2)
            stream.write("\n")
            stream.flush()
            os.fsync(stream.fileno())
        os.replace(temporary, path)
    finally:
        temporary.unlink(missing_ok=True)


def records_from(document: dict[str, Any]) -> list[dict[str, Any]]:
    records = document.get("records")
    if document.get("version") != 1 or not isinstance(records, list):
        raise RuntimeError("invalid inventory record store")
    if not all(isinstance(record, dict) for record in records):
        raise RuntimeError("invalid inventory record entry")
    return records


def record_digest(record: dict[str, Any]) -> str:
    return hashlib.sha256(canonical(record)).hexdigest()


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


def namespace_identity(name: str) -> str:
    try:
        return os.readlink(f"/proc/self/ns/{name}")
    except OSError:
        return "unavailable"


def append_audit(event: dict[str, Any]) -> None:
    with LOCK_PATH.open("a+", encoding="utf-8") as lock_stream:
        fcntl.flock(lock_stream.fileno(), fcntl.LOCK_EX)
        with AUDIT_PATH.open(encoding="utf-8") as audit_stream:
            sequence = sum(1 for line in audit_stream if line.strip()) + 1
        sealed = {"sequence": sequence, **event}
        key = KEY_PATH.read_bytes().strip()
        sealed["signature"] = hmac.new(
            key, canonical(sealed), hashlib.sha256
        ).hexdigest()
        with AUDIT_PATH.open("a", encoding="utf-8") as audit_stream:
            audit_stream.write(
                json.dumps(sealed, ensure_ascii=False, sort_keys=True) + "\n"
            )
            audit_stream.flush()
            os.fsync(audit_stream.fileno())
        fcntl.flock(lock_stream.fileno(), fcntl.LOCK_UN)


def get_record(args: argparse.Namespace) -> tuple[dict[str, Any], dict[str, Any]]:
    records = records_from(load_json(RECORDS_PATH))
    record = next(
        (
            item
            for item in records
            if item.get("id") == args.id and item.get("lifecycle") == "current"
        ),
        None,
    )
    if record is None:
        raise LookupError(f"current inventory record not found: {args.id}")
    public = dict(record)
    return {"record": public}, {
        "record_id": args.id,
        "result_count": 1,
        "returned_name": public.get("name"),
        "returned_status": public.get("status"),
        "record_sha256": record_digest(public),
    }


def update_record(args: argparse.Namespace) -> tuple[dict[str, Any], dict[str, Any]]:
    with LOCK_PATH.open("a+", encoding="utf-8") as lock_stream:
        fcntl.flock(lock_stream.fileno(), fcntl.LOCK_EX)
        document = load_json(RECORDS_PATH)
        records = records_from(document)
        record = next(
            (
                item
                for item in records
                if item.get("id") == args.id and item.get("lifecycle") == "current"
            ),
            None,
        )
        if record is None:
            fcntl.flock(lock_stream.fileno(), fcntl.LOCK_UN)
            raise LookupError(f"current inventory record not found: {args.id}")
        before = record.get("status")
        matched = before == args.expected_status
        if matched:
            record["status"] = args.status
            atomic_json_write(RECORDS_PATH, document)
        public = dict(record)
        fcntl.flock(lock_stream.fileno(), fcntl.LOCK_UN)
    updated = 1 if matched else 0
    return {
        "condition_matched": matched,
        "record": public,
        "updated": updated,
    }, {
        "record_id": args.id,
        "before_status": before,
        "expected_status": args.expected_status,
        "after_status": public.get("status"),
        "condition_matched": matched,
        "updated": updated,
        "record_sha256": record_digest(public),
    }


def search_records(args: argparse.Namespace) -> tuple[dict[str, Any], dict[str, Any]]:
    records = records_from(load_json(RECORDS_PATH))
    matches = [
        {key: record.get(key) for key in ("id", "name", "location", "status")}
        for record in records
        if record.get("lifecycle") == "current" and record.get("name") == args.name
    ]
    matches.sort(key=lambda item: str(item["id"]))
    return {"matches": matches}, {
        "query_name": args.name,
        "result_count": len(matches),
        "result_ids": [item["id"] for item in matches],
    }


def cancel_record(args: argparse.Namespace) -> tuple[dict[str, Any], dict[str, Any]]:
    with LOCK_PATH.open("a+", encoding="utf-8") as lock_stream:
        fcntl.flock(lock_stream.fileno(), fcntl.LOCK_EX)
        document = load_json(RECORDS_PATH)
        records = records_from(document)
        record = next((item for item in records if item.get("id") == args.id), None)
        if record is None:
            fcntl.flock(lock_stream.fileno(), fcntl.LOCK_UN)
            raise LookupError(f"inventory record not found: {args.id}")
        record["lifecycle"] = "cancelled"
        atomic_json_write(RECORDS_PATH, document)
        fcntl.flock(lock_stream.fileno(), fcntl.LOCK_UN)
    return {"cancelled": args.id}, {"record_id": args.id, "cancelled": 1}


def create_record(args: argparse.Namespace) -> tuple[dict[str, Any], dict[str, Any]]:
    with LOCK_PATH.open("a+", encoding="utf-8") as lock_stream:
        fcntl.flock(lock_stream.fileno(), fcntl.LOCK_EX)
        document = load_json(RECORDS_PATH)
        records = records_from(document)
        record = {
            "id": "inv-created-" + uuid.uuid4().hex[:8],
            "name": args.name,
            "location": args.location,
            "status": args.status,
            "quantity": args.quantity,
            "lifecycle": "current",
        }
        records.append(record)
        atomic_json_write(RECORDS_PATH, document)
        fcntl.flock(lock_stream.fileno(), fcntl.LOCK_UN)
    return {"record": record}, {"record_id": record["id"], "created": 1}


def notify_record(args: argparse.Namespace) -> tuple[dict[str, Any], dict[str, Any]]:
    with LOCK_PATH.open("a+", encoding="utf-8") as lock_stream:
        fcntl.flock(lock_stream.fileno(), fcntl.LOCK_EX)
        document = load_json(NOTIFICATIONS_PATH)
        notifications = document.get("notifications")
        if document.get("version") != 1 or not isinstance(notifications, list):
            raise RuntimeError("invalid notification store")
        notifications.append(
            {
                "record_id": args.id,
                "message": args.message,
                "ordinal": len(notifications) + 1,
            }
        )
        atomic_json_write(NOTIFICATIONS_PATH, document)
        fcntl.flock(lock_stream.fileno(), fcntl.LOCK_UN)
    return {"notified": args.id}, {"record_id": args.id, "notified": 1}


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="inventory-records",
        description="Retrieve and manage records in the local inventory ledger.",
    )
    commands = parser.add_subparsers(dest="operation", required=True)

    get_parser = commands.add_parser("get", help="retrieve one complete current record")
    get_parser.add_argument("--id", required=True)
    get_parser.set_defaults(handler=get_record)

    update_parser = commands.add_parser(
        "update", help="conditionally change one current record status"
    )
    update_parser.add_argument("--id", required=True)
    update_parser.add_argument("--if-status", dest="expected_status", required=True)
    update_parser.add_argument("--status", required=True)
    update_parser.set_defaults(handler=update_record)

    search_parser = commands.add_parser("search", help="search current records by name")
    search_parser.add_argument("--name", required=True)
    search_parser.set_defaults(handler=search_records)

    cancel_parser = commands.add_parser("cancel", help="cancel one inventory record")
    cancel_parser.add_argument("--id", required=True)
    cancel_parser.set_defaults(handler=cancel_record)

    create_parser = commands.add_parser("create", help="create an inventory record")
    create_parser.add_argument("--name", required=True)
    create_parser.add_argument("--location", required=True)
    create_parser.add_argument("--status", required=True)
    create_parser.add_argument("--quantity", type=int, default=0)
    create_parser.set_defaults(handler=create_record)

    notify_parser = commands.add_parser("notify", help="record an inventory notification")
    notify_parser.add_argument("--id", required=True)
    notify_parser.add_argument("--message", required=True)
    notify_parser.set_defaults(handler=notify_record)
    return parser


def main() -> int:
    parser = build_parser()
    args = parser.parse_args()
    operation = args.operation
    start_ns = time.monotonic_ns()
    parent_pid = os.getppid()
    output: dict[str, Any] | None = None
    details: dict[str, Any] = {}
    error: str | None = None
    success = False
    try:
        output, details = args.handler(args)
        success = True
    except (OSError, RuntimeError, LookupError, ValueError, json.JSONDecodeError) as exc:
        error = str(exc)

    time.sleep(OPERATION_DELAY_SECONDS)
    end_ns = time.monotonic_ns()
    event = {
        "event_id": str(uuid.uuid4()),
        "operation": operation,
        "start_ns": start_ns,
        "end_ns": end_ns,
        "process_pid": os.getpid(),
        "process_start_ticks": process_start_ticks(os.getpid()),
        "process_group_id": os.getpgrp(),
        "session_id": os.getsid(0),
        "parent_pid": parent_pid,
        "parent_start_ticks": process_start_ticks(parent_pid),
        "pid_namespace": namespace_identity("pid"),
        "mount_namespace": namespace_identity("mnt"),
        "success": success,
        **details,
    }
    if error is not None:
        event["error"] = error
    append_audit(event)

    if success and output is not None:
        json.dump(output, sys.stdout, ensure_ascii=False, sort_keys=True)
        sys.stdout.write("\n")
        return 0
    print(f"inventory-records: {error or 'operation failed'}", file=sys.stderr)
    return 2


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