#!/usr/bin/env python3
"""Executable interface to the sandboxed project task registry."""

from __future__ import annotations

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


ROOT = Path(__file__).resolve().parent
DATA_PATH = ROOT / ".projects" / "records.json"
RUNTIME = ROOT / ".harness" / "runtime"
AUDIT_PATH = RUNTIME / "project-audit.jsonl"
AUDIT_KEY = b"moonshiner-pi-project-audit-error-recovery-0090-v1"
READ_DELAY_SECONDS = 0.35
TRANSIENT_TARGET = (
    "Water sampling protocol",
    "River Program",
    "2026-09-24",
)
OPERATIONS = (
    "availability",
    "get",
    "create",
    "update",
    "cancel",
    "notify",
)


class TransientFailure(Exception):
    pass


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()
    RUNTIME.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 load_payload() -> dict:
    with DATA_PATH.open(encoding="utf-8") as stream:
        return json.load(stream)


def save_payload(payload: dict) -> None:
    temporary = DATA_PATH.with_suffix(".json.tmp")
    temporary.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
    os.replace(temporary, DATA_PATH)


def operation_parser(operation: str) -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(prog=f"project-registry {operation}")
    if operation == "availability":
        parser.add_argument("--name", required=True)
        parser.add_argument("--location", required=True)
        parser.add_argument("--date", required=True)
    elif operation == "get":
        parser.add_argument("--id", dest="stable_id", required=True)
    elif operation == "create":
        parser.add_argument("--name", required=True)
        parser.add_argument("--location", required=True)
        parser.add_argument("--date", required=True)
        parser.add_argument("--availability", required=True)
    elif operation in {"update", "cancel"}:
        parser.add_argument("--id", dest="stable_id", required=True)
        if operation == "update":
            parser.add_argument("--availability", required=True)
    elif operation == "notify":
        parser.add_argument("--id", dest="stable_id", required=True)
        parser.add_argument("--message", required=True)
    return parser


def unique_task(payload: dict, stable_id: str) -> dict:
    matches = [row for row in payload["tasks"] if row["stable_id"] == stable_id]
    if len(matches) != 1:
        raise LookupError(f"stable ID not found: {stable_id}")
    return matches[0]


def consume_transient_once(name: str, location: str, date: str) -> bool:
    if (name, location, date) != TRANSIENT_TARGET:
        return False
    RUNTIME.mkdir(parents=True, exist_ok=True)
    marker = RUNTIME / "transient-consumed"
    try:
        descriptor = os.open(marker, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
    except FileExistsError:
        return False
    with os.fdopen(descriptor, "w", encoding="utf-8") as stream:
        stream.write("consumed\n")
        stream.flush()
        os.fsync(stream.fileno())
    return True


def execute(operation: str, argv: list[str]) -> tuple[dict, dict]:
    payload = load_payload()
    args = operation_parser(operation).parse_args(argv)

    if operation == "availability":
        details = {
            "name": args.name,
            "location": args.location,
            "date": args.date,
        }
        matches = [
            row
            for row in payload["tasks"]
            if row["name"] == args.name
            and row["location"] == args.location
            and row["date"] == args.date
        ]
        if len(matches) != 1:
            raise LookupError("no unique task matches the requested fields")
        if consume_transient_once(args.name, args.location, args.date):
            raise TransientFailure("project service temporarily unavailable")
        record = matches[0]
        details["availability"] = record["availability"]
        return {
            "name": record["name"],
            "location": record["location"],
            "date": record["date"],
            "availability": record["availability"],
        }, details

    if operation == "get":
        record = unique_task(payload, args.stable_id)
        return {"record": record}, {"stable_id": args.stable_id, "result_count": 1}

    if operation == "create":
        stable_id = "pro-" + uuid.uuid4().hex[:6]
        record = {
            "stable_id": stable_id,
            "name": args.name,
            "location": args.location,
            "date": args.date,
            "availability": args.availability,
            "status": "new",
        }
        payload["tasks"].append(record)
        save_payload(payload)
        return {"record": record}, {"stable_id": stable_id, "result_count": 1}

    if operation in {"update", "cancel"}:
        record = unique_task(payload, args.stable_id)
        if operation == "update":
            record["availability"] = args.availability
        else:
            record["availability"] = "unavailable"
            record["status"] = "cancelled"
        save_payload(payload)
        return {"record": record}, {"stable_id": args.stable_id, "result_count": 1}

    if operation == "notify":
        unique_task(payload, args.stable_id)
        notice = {"stable_id": args.stable_id, "message": args.message}
        payload["notifications"].append(notice)
        save_payload(payload)
        return {"notification": notice}, {"stable_id": args.stable_id}

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


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


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

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

    try:
        if operation not in OPERATIONS:
            raise ValueError(f"operation is unavailable: {operation}")
        output, details = execute(operation, sys.argv[2:])
        success = True
    except TransientFailure as exc:
        error = str(exc)
        error_kind = "transient"
        if len(sys.argv) >= 8 and operation == "availability":
            parsed = operation_parser(operation).parse_args(sys.argv[2:])
            details = {
                "name": parsed.name,
                "location": parsed.location,
                "date": parsed.date,
            }
    except (SystemExit, ValueError, LookupError, OSError, json.JSONDecodeError) as exc:
        error = str(exc)
        error_kind = "permanent"

    time.sleep(READ_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),
        "success": success,
        **details,
    }
    if error is not None:
        event["error"] = error
        event["error_kind"] = error_kind
    append_audit(event)

    if success:
        print(json.dumps(output, sort_keys=True))
        return 0
    print(
        json.dumps({"error": error or "operation failed", "kind": error_kind}),
        file=sys.stderr,
    )
    return 75 if error_kind == "transient" else 2


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