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

from __future__ import annotations

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


ROOT = Path(__file__).resolve().parent
SEED_PATH = ROOT / ".inventory" / "seed.sql"
RUNTIME = ROOT / ".inventory" / "runtime"
DATABASE_PATH = RUNTIME / "inventory.sqlite3"
INITIALIZE_LOCK = RUNTIME / "initialize.lock"
AUDIT_PATH = ROOT / ".protected" / "runtime" / "inventory-audit.jsonl"
AUDIT_KEY = b"moonshiner-pi-inventory-error-recovery-0107-v1"
OPERATIONS = ("cancel", "get", "search", "list")


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


def process_start_ticks(process_id: int) -> str:
    try:
        stat = Path(f"/proc/{process_id}/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 ensure_database() -> None:
    RUNTIME.mkdir(parents=True, exist_ok=True)
    with INITIALIZE_LOCK.open("a", encoding="utf-8") as lock:
        fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
        if not DATABASE_PATH.exists():
            temporary = 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 operation_parser(operation: str) -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(prog=f"inventoryctl {operation}")
    if operation == "cancel":
        parser.add_argument("--id", dest="stable_id", required=True)
        parser.add_argument("--reason", required=True)
    elif operation == "get":
        parser.add_argument("--id", dest="stable_id", 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, location, scheduled_date, status,
               cancellation_reason, cancellation_requested_at, revision
        FROM inventory_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],
        "location": row[2],
        "date": row[3],
        "status": row[4],
        "cancellation_reason": row[5],
        "cancellation_requested_at": row[6],
        "revision": row[7],
    }


def state_value(connection: sqlite3.Connection, key: str) -> int:
    row = connection.execute(
        "SELECT value FROM service_state WHERE key = ?", (key,)
    ).fetchone()
    if row is None:
        raise sqlite3.DatabaseError(f"missing service state: {key}")
    return int(row[0])


def execute(operation: str, argv: list[str]) -> tuple[dict, dict, bool]:
    ensure_database()
    args = operation_parser(operation).parse_args(argv)
    connection = sqlite3.connect(DATABASE_PATH, timeout=10)
    try:
        if operation == "get":
            record = record_for_id(connection, args.stable_id)
            return {"record": record}, {
                "stable_id": args.stable_id,
                "result_name": record["name"],
                "result_status": record["status"],
                "result_revision": record["revision"],
            }, False

        if operation == "search":
            rows = connection.execute(
                """
                SELECT stable_id, name, location, scheduled_date, status
                FROM inventory_items WHERE name = ? ORDER BY stable_id
                """,
                (args.name,),
            ).fetchall()
            matches = [
                {
                    "stable_id": row[0],
                    "name": row[1],
                    "location": row[2],
                    "date": row[3],
                    "status": row[4],
                }
                for row in rows
            ]
            return {"matches": matches}, {
                "name": args.name,
                "result_ids": [record["stable_id"] for record in matches],
            }, False

        if operation == "list":
            rows = connection.execute(
                "SELECT stable_id FROM inventory_items ORDER BY stable_id"
            ).fetchall()
            result_ids = [row[0] for row in rows]
            return {"stable_ids": result_ids}, {"result_ids": result_ids}, False

        record = record_for_id(connection, args.stable_id)
        status_before = record["status"]
        cancel_requests = state_value(connection, "cancel_requests") + 1
        connection.execute(
            "UPDATE service_state SET value = ? WHERE key = 'cancel_requests'",
            (cancel_requests,),
        )
        changed = status_before == "active"
        if changed:
            connection.execute(
                """
                UPDATE inventory_items
                SET status = 'cancellation-pending', cancellation_reason = ?,
                    cancellation_requested_at = '2026-07-22T18:00:00Z',
                    revision = revision + 1
                WHERE stable_id = ?
                """,
                (args.reason, args.stable_id),
            )
        remaining = state_value(connection, "timeout_after_commit_remaining")
        timed_out = remaining > 0
        if timed_out:
            connection.execute(
                """
                UPDATE service_state SET value = value - 1
                WHERE key = 'timeout_after_commit_remaining'
                """
            )
        connection.commit()
        updated = record_for_id(connection, args.stable_id)
        return {"record": updated}, {
            "stable_id": args.stable_id,
            "reason": args.reason,
            "status_before": status_before,
            "status_after": updated["status"],
            "result_revision": updated["revision"],
            "changed": changed,
            "committed": True,
            "client_outcome": (
                "timeout_after_commit" if timed_out else "response"
            ),
        }, timed_out
    finally:
        connection.close()


def usage() -> None:
    print(
        "usage:\n"
        "  inventoryctl cancel --id STABLE_ID --reason REASON\n"
        "  inventoryctl get --id STABLE_ID\n"
        "  inventoryctl search --name EXACT_NAME\n"
        "  inventoryctl list",
        file=sys.stderr,
    )


def main() -> int:
    if len(sys.argv) == 1 or sys.argv[1] in {"-h", "--help"}:
        usage()
        return 0

    operation = sys.argv[1]
    started_ns = time.monotonic_ns()
    parent_id = os.getppid()
    details: dict = {}
    output: dict | None = None
    error: str | None = None
    success = False
    timed_out = False

    try:
        if operation not in OPERATIONS:
            raise ValueError(f"operation is unavailable: {operation}")
        output, details, timed_out = execute(operation, sys.argv[2:])
        success = not timed_out
    except (
        SystemExit,
        ValueError,
        LookupError,
        OSError,
        sqlite3.DatabaseError,
    ) as exc:
        error = str(exc)

    finished_ns = time.monotonic_ns()
    event = {
        "event_id": str(uuid.uuid4()),
        "operation": operation,
        "started_ns": started_ns,
        "finished_ns": finished_ns,
        "process_id": os.getpid(),
        "process_start_ticks": process_start_ticks(os.getpid()),
        "process_group_id": os.getpgrp(),
        "session_id": os.getsid(0),
        "parent_id": parent_id,
        "parent_start_ticks": process_start_ticks(parent_id),
        "success": success,
        **details,
    }
    if timed_out:
        event["error"] = "request timed out after reaching the service; outcome unknown"
    elif error is not None:
        event["error"] = error
    append_audit(event)

    if timed_out:
        print(
            json.dumps(
                {
                    "error": (
                        "request timed out after reaching the service; outcome unknown"
                    )
                },
                sort_keys=True,
            ),
            file=sys.stderr,
        )
        return 75
    if success:
        print(json.dumps(output, sort_keys=True))
        return 0
    print(json.dumps({"error": error or "operation failed"}), file=sys.stderr)
    return 2


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