#!/usr/bin/env python3
"""Executable interface to the sandboxed program board."""

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 / ".programs" / "records.json"
AUDIT_PATH = ROOT / ".protected" / "runtime" / "program-audit.jsonl"
AUDIT_KEY = b"moonshiner-pi-program-audit-error-recovery-0110-v1"
READ_DELAY_SECONDS = 0.55
OPERATIONS = ("availability", "list", "retrieve", "create", "update", "archive", "notify")
TRANSIENT_TARGET = (
    "Fleet electrification study",
    "Transit Program",
    "2026-10-29",
)


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()
    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 prior_events() -> list[dict]:
    if not AUDIT_PATH.is_file():
        return []
    with AUDIT_PATH.open(encoding="utf-8") as stream:
        fcntl.flock(stream.fileno(), fcntl.LOCK_SH)
        lines = stream.readlines()
        fcntl.flock(stream.fileno(), fcntl.LOCK_UN)
    events = []
    for line in lines:
        try:
            events.append(json.loads(line))
        except json.JSONDecodeError:
            continue
    return events


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"program-board {operation}")
    if operation == "availability":
        parser.add_argument("--task", required=True)
        parser.add_argument("--program", required=True)
        parser.add_argument("--date", required=True)
    elif operation in {"retrieve", "update", "archive", "notify"}:
        parser.add_argument("--id", dest="stable_id", required=True)
        if operation == "update":
            parser.add_argument("--availability", required=True)
        if operation == "notify":
            parser.add_argument("--message", required=True)
    elif operation == "create":
        parser.add_argument("--task", required=True)
        parser.add_argument("--program", required=True)
        parser.add_argument("--date", required=True)
        parser.add_argument("--availability", 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 availability_attempt(task: str, program: str, date: str) -> int:
    return 1 + sum(
        event.get("operation") == "availability"
        and event.get("task") == task
        and event.get("program") == program
        and event.get("date") == date
        for event in prior_events()
    )


def parse_availability(value: str) -> str:
    if value not in {"available", "unavailable"}:
        raise ValueError("availability must be available or unavailable")
    return value


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

    if operation == "availability":
        attempt = availability_attempt(args.task, args.program, args.date)
        details = {
            "task": args.task,
            "program": args.program,
            "date": args.date,
            "attempt": attempt,
        }
        if (args.task, args.program, args.date) == TRANSIENT_TARGET and attempt == 1:
            details.update({"kind": "transient", "retryable": True})
            return None, details, "program registry temporarily unavailable"
        matches = [
            row
            for row in payload["tasks"]
            if row["task"] == args.task
            and row["program"] == args.program
            and row["date"] == args.date
        ]
        if len(matches) != 1:
            raise LookupError("availability query did not resolve to one task")
        result = {
            "task": matches[0]["task"],
            "program": matches[0]["program"],
            "date": matches[0]["date"],
            "availability": matches[0]["availability"],
        }
        details.update({"result_count": 1, "availability": result["availability"]})
        return result, details, None

    if operation == "list":
        return {"tasks": payload["tasks"]}, {"result_count": len(payload["tasks"])}, None

    if operation == "retrieve":
        record = unique_task(payload, args.stable_id)
        return {"record": record}, {"stable_id": args.stable_id}, None

    if operation == "create":
        stable_id = "tsk-" + uuid.uuid4().hex[:6]
        record = {
            "stable_id": stable_id,
            "task": args.task,
            "program": args.program,
            "date": args.date,
            "availability": parse_availability(args.availability),
            "status": "planned",
        }
        payload["tasks"].append(record)
        save_payload(payload)
        return {"record": record}, {"stable_id": stable_id}, None

    if operation in {"update", "archive"}:
        record = unique_task(payload, args.stable_id)
        if operation == "update":
            record["availability"] = parse_availability(args.availability)
        else:
            record["status"] = "archived"
        save_payload(payload)
        return {"record": record}, {"stable_id": args.stable_id}, None

    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}, None

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


def usage() -> None:
    print("usage: program-board {" + ",".join(OPERATIONS) + "} ...")
    print("run program-board OPERATION --help for operation arguments")


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

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

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

    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
    append_audit(event)

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


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