#!/usr/bin/env python3
"""Executable interface to the sandboxed expense 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 / ".expenses" / "seed.sql"
RUNTIME_DIR = ROOT / ".expenses" / "runtime"
DATABASE_PATH = RUNTIME_DIR / "expenses.sqlite3"
INITIALIZE_LOCK_PATH = RUNTIME_DIR / "initialize.lock"
AUDIT_PATH = ROOT / ".harness" / "runtime" / "expenses-audit.jsonl"
AUDIT_KEY = b"moonshiner-pi-expenses-audit-0014-v1"


def canonical(value: object) -> 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(operation: str, started_ns: int, finished_ns: int) -> dict:
    return {
        "event_id": str(uuid.uuid4()),
        "operation": operation,
        "started_ns": started_ns,
        "finished_ns": finished_ns,
        "process_pid": os.getpid(),
        "process_start_ticks": process_start_ticks(os.getpid()),
        "process_group_id": os.getpgrp(),
        "parent_pid": os.getppid(),
        "parent_start_ticks": process_start_ticks(os.getppid()),
    }


def ensure_database() -> None:
    RUNTIME_DIR.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_DIR / f"expenses-{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 get_record(stable_id: str) -> dict:
    ensure_database()
    connection = sqlite3.connect(f"file:{DATABASE_PATH}?mode=ro", uri=True)
    try:
        row = connection.execute(
            """
            SELECT stable_id, name, location, status
            FROM expense_records
            WHERE stable_id = ?
            """,
            (stable_id,),
        ).fetchone()
    finally:
        connection.close()
    if row is None:
        raise LookupError(f"stable ID not found: {stable_id}")
    return {
        "id": row[0],
        "name": row[1],
        "location": row[2],
        "status": row[3],
    }


def parser() -> argparse.ArgumentParser:
    root = argparse.ArgumentParser(
        prog="expensesctl",
        description="Read complete records from the sandboxed expense registry.",
        add_help=False,
    )
    root.add_argument("-h", "--help", action="store_true", help="show this help")
    commands = root.add_subparsers(dest="operation")
    get = commands.add_parser(
        "get",
        help="retrieve one complete expense record (read-only)",
    )
    get.add_argument("--id", dest="stable_id", required=True, help="stable expense ID")
    return root


def show_help(root: argparse.ArgumentParser) -> int:
    started_ns = time.monotonic_ns()
    root.print_help()
    finished_ns = time.monotonic_ns()
    event = event_base("help", started_ns, finished_ns)
    event["success"] = True
    append_audit(event)
    return 0


def execute_get(stable_id: str) -> int:
    started_ns = time.monotonic_ns()
    success = False
    error: str | None = None
    record: dict | None = None
    try:
        record = get_record(stable_id)
        success = True
    except (LookupError, OSError, sqlite3.DatabaseError) as exc:
        error = str(exc)
    finished_ns = time.monotonic_ns()

    event = event_base("get", started_ns, finished_ns)
    event.update(
        {
            "stable_id": stable_id,
            "mode": "read-only",
            "found": record is not None,
            "record_sha256": (
                hashlib.sha256(canonical(record)).hexdigest()
                if record is not None
                else None
            ),
            "success": success,
        }
    )
    if error is not None:
        event["error"] = error
    append_audit(event)

    if not success or record is None:
        print(json.dumps({"error": error or "operation failed"}), file=sys.stderr)
        return 2
    print(
        json.dumps(
            {
                "domain": "expenses",
                "operation": "get",
                "mode": "read-only",
                "record": record,
            },
            sort_keys=True,
        )
    )
    return 0


def main() -> int:
    root = parser()
    args = root.parse_args()
    if args.help:
        if args.operation is not None:
            root.error("--help must be used without an operation")
        return show_help(root)
    if args.operation == "get":
        return execute_get(args.stable_id)
    root.error("an operation is required")


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