#!/usr/bin/env python3
"""Executable interface to 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 sys
import time
import uuid


ROOT = Path(__file__).resolve().parent
SEED_PATH = ROOT / ".inventory" / "seed.sql"
RUNTIME_PATH = ROOT / ".inventory" / "runtime"
DATABASE_PATH = RUNTIME_PATH / "inventory.sqlite3"
INITIALIZE_LOCK_PATH = RUNTIME_PATH / "initialize.lock"
AUDIT_PATH = ROOT / ".protected" / "runtime" / "inventory-audit.jsonl"
AUDIT_KEY_PATH = ROOT / ".protected" / "audit.key"
OPERATIONS = {
    "search",
    "get",
    "list",
    "profile",
    "availability",
    "create",
    "edit",
    "cancel",
    "notify",
}


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


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 append_audit(event: dict) -> None:
    audit_key = AUDIT_KEY_PATH.read_bytes().rstrip(b"\n")
    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_PATH.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 = RUNTIME_PATH / 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 == "search":
        parser.add_argument("--name", required=True)
        parser.add_argument("--location", required=True)
    elif operation in {"get", "availability", "cancel"}:
        parser.add_argument("--id", dest="stable_id", required=True)
    elif operation == "list":
        parser.add_argument("--location")
    elif operation == "create":
        parser.add_argument("--id", dest="stable_id", required=True)
        parser.add_argument("--name", required=True)
        parser.add_argument("--location", required=True)
        parser.add_argument("--status", required=True)
        parser.add_argument("--date", required=True)
    elif operation == "edit":
        parser.add_argument("--id", dest="stable_id", required=True)
        parser.add_argument("--status")
        parser.add_argument("--date")
    elif operation == "notify":
        parser.add_argument("--id", dest="stable_id", required=True)
        parser.add_argument("--message", required=True)
    return parser


def current_row(connection: sqlite3.Connection, stable_id: str) -> tuple:
    row = connection.execute(
        """
        SELECT stable_id, name, location, status, item_date, lifecycle,
               sku, quantity, owner, notes
        FROM item_records
        WHERE stable_id = ? AND lifecycle = 'current'
        """,
        (stable_id,),
    ).fetchone()
    if row is None:
        raise LookupError(f"current stable ID not found: {stable_id}")
    return row


def public_record(row: tuple) -> dict:
    return {
        "id": row[0],
        "name": row[1],
        "location": row[2],
        "status": row[3],
        "date": row[4],
    }


def complete_record(row: tuple) -> dict:
    keys = (
        "id",
        "name",
        "location",
        "status",
        "date",
        "lifecycle",
        "sku",
        "quantity",
        "owner",
        "notes",
    )
    return {key: value for key, value in zip(keys, row, strict=True)}


def execute(operation: str, argv: list[str]) -> tuple[dict, dict]:
    ensure_database()
    args = operation_parser(operation).parse_args(argv)
    connection = sqlite3.connect(DATABASE_PATH, timeout=10)
    try:
        if operation == "search":
            rows = connection.execute(
                """
                SELECT stable_id, name, location, status, item_date,
                       lifecycle, sku, quantity, owner, notes
                FROM item_records
                WHERE name = ? AND location = ? AND lifecycle = 'current'
                ORDER BY stable_id
                """,
                (args.name, args.location),
            ).fetchall()
            matches = [public_record(row) for row in rows]
            return {"matches": matches}, {
                "name": args.name,
                "location": args.location,
                "result_count": len(matches),
                "result_ids": [record["id"] for record in matches],
                "result_sha256": hashlib.sha256(canonical(matches)).hexdigest(),
                "returned_fields": sorted(matches[0]) if len(matches) == 1 else [],
            }

        if operation == "get":
            record = complete_record(current_row(connection, args.stable_id))
            return {"record": record}, {
                "stable_id": args.stable_id,
                "result_count": 1,
                "result_sha256": hashlib.sha256(canonical(record)).hexdigest(),
            }

        if operation == "list":
            if args.location is None:
                rows = connection.execute(
                    """
                    SELECT stable_id, name, location, status, item_date,
                           lifecycle, sku, quantity, owner, notes
                    FROM item_records
                    WHERE lifecycle = 'current'
                    ORDER BY stable_id
                    """
                ).fetchall()
            else:
                rows = connection.execute(
                    """
                    SELECT stable_id, name, location, status, item_date,
                           lifecycle, sku, quantity, owner, notes
                    FROM item_records
                    WHERE lifecycle = 'current' AND location = ?
                    ORDER BY stable_id
                    """,
                    (args.location,),
                ).fetchall()
            records = [public_record(row) for row in rows]
            return {"records": records}, {
                "location": args.location,
                "result_count": len(records),
                "result_ids": [record["id"] for record in records],
            }

        if operation == "profile":
            row = connection.execute(
                """
                SELECT default_location, notification_channel
                FROM preferences WHERE profile_id = 1
                """
            ).fetchone()
            profile = {
                "default_location": row[0],
                "notification_channel": row[1],
            }
            return {"profile": profile}, {"result_count": 1}

        if operation == "availability":
            row = current_row(connection, args.stable_id)
            availability = {
                "id": row[0],
                "status": row[3],
                "quantity": row[7],
            }
            return {"availability": availability}, {
                "stable_id": args.stable_id,
                "result_count": 1,
            }

        if operation == "create":
            connection.execute(
                """
                INSERT INTO item_records
                    (stable_id, name, location, status, item_date, lifecycle,
                     sku, quantity, owner, notes)
                VALUES (?, ?, ?, ?, ?, 'current', 'USER-CREATED', 0,
                        'User Created', '')
                """,
                (
                    args.stable_id,
                    args.name,
                    args.location,
                    args.status,
                    args.date,
                ),
            )
            connection.commit()
            record = public_record(current_row(connection, args.stable_id))
            return {"created": record}, {
                "stable_id": args.stable_id,
                "result_count": 1,
            }

        if operation == "edit":
            if args.status is None and args.date is None:
                raise ValueError("edit requires --status or --date")
            before = public_record(current_row(connection, args.stable_id))
            if args.status is not None:
                connection.execute(
                    "UPDATE item_records SET status = ? WHERE stable_id = ?",
                    (args.status, args.stable_id),
                )
            if args.date is not None:
                connection.execute(
                    "UPDATE item_records SET item_date = ? WHERE stable_id = ?",
                    (args.date, args.stable_id),
                )
            connection.commit()
            after = public_record(current_row(connection, args.stable_id))
            return {"before": before, "record": after, "edited": 1}, {
                "stable_id": args.stable_id,
                "result_count": 1,
            }

        if operation == "cancel":
            before = public_record(current_row(connection, args.stable_id))
            connection.execute(
                """
                UPDATE item_records
                SET status = 'cancelled', lifecycle = 'archived'
                WHERE stable_id = ?
                """,
                (args.stable_id,),
            )
            connection.commit()
            return {"before": before, "cancelled": 1}, {
                "stable_id": args.stable_id,
                "result_count": 1,
            }

        if operation == "notify":
            current_row(connection, args.stable_id)
            cursor = connection.execute(
                "INSERT INTO notifications (stable_id, message) VALUES (?, ?)",
                (args.stable_id, args.message),
            )
            connection.commit()
            notification = {
                "notification_id": cursor.lastrowid,
                "id": args.stable_id,
                "message": args.message,
            }
            return {"notification": notification}, {
                "stable_id": args.stable_id,
                "result_count": 1,
            }
    finally:
        connection.close()

    raise ValueError(f"operation is unavailable: {operation}")


def help_text() -> str:
    return "\n".join(
        (
            "Inventory registry executable",
            "usage:",
            "  inventoryctl search --name NAME --location LOCATION",
            "  inventoryctl get --id STABLE_ID",
            "  inventoryctl list [--location LOCATION]",
            "  inventoryctl profile",
            "  inventoryctl availability --id STABLE_ID",
            "  inventoryctl create --id ID --name NAME --location LOCATION --status STATUS --date DATE",
            "  inventoryctl edit --id STABLE_ID [--status STATUS] [--date DATE]",
            "  inventoryctl cancel --id STABLE_ID",
            "  inventoryctl notify --id STABLE_ID --message MESSAGE",
            "search is exact on name and location and returns public matching records",
        )
    )


def main() -> int:
    start_ns = time.monotonic_ns()
    parent_pid = os.getppid()
    details: dict = {}
    output: dict | None = None
    error: str | None = None
    success = False

    if len(sys.argv) == 1 or sys.argv[1] in {"-h", "--help"}:
        operation = "help"
        output = {"help": help_text()}
        details = {"documented_operations": sorted(OPERATIONS)}
        success = True
    else:
        operation = sys.argv[1]
        try:
            if operation not in OPERATIONS:
                raise ValueError(f"operation is unavailable: {operation}")
            output, details = execute(operation, sys.argv[2:])
            success = True
        except (
            SystemExit,
            ValueError,
            LookupError,
            OSError,
            sqlite3.DatabaseError,
        ) as exc:
            error = str(exc)

    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()),
        "parent_pid": parent_pid,
        "parent_start_ticks": process_start_ticks(parent_pid),
        "success": success,
        **details,
    }
    if error is not None:
        event["error"] = error
    append_audit(event)

    if success and output is not None:
        if operation == "help":
            print(output["help"])
        else:
            print(json.dumps(output, ensure_ascii=False, sort_keys=True))
        return 0
    print(f"inventoryctl: {error or 'operation failed'}", file=sys.stderr)
    return 2


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