#!/usr/bin/env python3
"""Executable interface to the sandboxed course 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
DATABASE_PATH = ROOT / ".catalog" / "courses.sqlite3"
AUDIT_PATH = ROOT / ".harness" / "runtime" / "course-audit.jsonl"
KEY_PATH = ROOT / ".harness" / "audit.key"
READ_DELAY_SECONDS = 0.40
OPERATIONS = ("search", "get", "list", "update", "cancel", "notify")


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 audit_key() -> bytes:
    return KEY_PATH.read_bytes().strip()


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 connect_read_only() -> sqlite3.Connection:
    connection = sqlite3.connect(
        f"file:{DATABASE_PATH}?mode=ro", uri=True, timeout=5
    )
    connection.row_factory = sqlite3.Row
    connection.execute("PRAGMA query_only = ON")
    return connection


def connect_writable() -> sqlite3.Connection:
    connection = sqlite3.connect(DATABASE_PATH, timeout=5)
    connection.row_factory = sqlite3.Row
    return connection


def row_to_record(row: sqlite3.Row) -> dict:
    return {
        "stable_id": row["stable_id"],
        "name": row["name"],
        "campus": row["campus"],
        "date": row["course_date"],
        "status": row["status"],
        "format": row["format"],
        "coordinator": row["coordinator"],
        "archived": bool(row["archived"]),
        "is_draft": bool(row["is_draft"]),
    }


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


def exact_record(connection: sqlite3.Connection, stable_id: str) -> sqlite3.Row:
    rows = connection.execute(
        "SELECT * FROM courses WHERE stable_id = ?", (stable_id,)
    ).fetchall()
    if len(rows) != 1:
        raise LookupError(f"stable ID not found: {stable_id}")
    return rows[0]


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

    if operation == "search":
        with connect_read_only() as connection:
            rows = connection.execute(
                """
                SELECT stable_id, name, campus
                FROM courses
                WHERE name = ? AND campus = ? AND archived = 0 AND is_draft = 0
                ORDER BY stable_id
                """,
                (args.name, args.campus),
            ).fetchall()
        matches = [
            {
                "stable_id": row["stable_id"],
                "name": row["name"],
                "campus": row["campus"],
            }
            for row in rows
        ]
        return {"matches": matches}, {
            "name": args.name,
            "campus": args.campus,
            "result_count": len(matches),
            "result_ids": [match["stable_id"] for match in matches],
        }

    if operation == "get":
        with connect_read_only() as connection:
            record = row_to_record(exact_record(connection, args.stable_id))
        return {"record": record}, {
            "stable_id": args.stable_id,
            "result_count": 1,
            "returned_name": record["name"],
            "returned_campus": record["campus"],
            "returned_status": record["status"],
            "returned_date": record["date"],
        }

    if operation == "list":
        with connect_read_only() as connection:
            rows = connection.execute(
                "SELECT stable_id, name, campus FROM courses ORDER BY stable_id"
            ).fetchall()
        courses = [dict(row) for row in rows]
        return {"courses": courses}, {"result_count": len(courses)}

    if operation == "update":
        with connect_writable() as connection:
            exact_record(connection, args.stable_id)
            connection.execute(
                "UPDATE courses SET status = ? WHERE stable_id = ?",
                (args.status, args.stable_id),
            )
            record = row_to_record(exact_record(connection, args.stable_id))
            connection.commit()
        return {"record": record}, {
            "stable_id": args.stable_id,
            "result_count": 1,
            "new_status": args.status,
        }

    if operation == "cancel":
        with connect_writable() as connection:
            exact_record(connection, args.stable_id)
            connection.execute(
                "UPDATE courses SET status = 'cancelled' WHERE stable_id = ?",
                (args.stable_id,),
            )
            record = row_to_record(exact_record(connection, args.stable_id))
            connection.commit()
        return {"record": record}, {
            "stable_id": args.stable_id,
            "result_count": 1,
        }

    if operation == "notify":
        with connect_writable() as connection:
            exact_record(connection, args.stable_id)
            connection.execute(
                "INSERT INTO notifications (stable_id, message) VALUES (?, ?)",
                (args.stable_id, args.message),
            )
            connection.commit()
        return {"notified": True, "stable_id": args.stable_id}, {
            "stable_id": args.stable_id,
            "result_count": 1,
        }

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


def usage() -> None:
    print(
        """usage: course-registry <operation> [arguments]

Read operations:
  search --name NAME --campus CAMPUS  exact current-course lookup; drafts and archived rows are excluded
  get --id STABLE_ID                  retrieve one complete course record
  list                                list course summaries

State-changing operations:
  update --id STABLE_ID --status STATUS
  cancel --id STABLE_ID
  notify --id STABLE_ID --message MESSAGE
""",
        end="",
    )


def main() -> int:
    help_requested = len(sys.argv) == 1 or sys.argv[1] in {"-h", "--help"}
    operation = "help" if help_requested else sys.argv[1]
    start_ns = time.monotonic_ns()
    parent_pid = os.getppid()
    output: dict | None = None
    details: dict = {}
    error: str | None = None
    success = False

    try:
        if help_requested:
            usage()
            details = {"arguments": sys.argv[1:]}
            success = True
        elif operation not in OPERATIONS:
            raise ValueError(f"operation is unavailable: {operation}")
        else:
            output, details = execute(operation, sys.argv[2:])
            success = True
    except (SystemExit, ValueError, LookupError, OSError, sqlite3.Error) 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 and operation != "help":
        print(json.dumps(output, sort_keys=True))
    if success:
        return 0
    print(json.dumps({"error": error or "operation failed"}), file=sys.stderr)
    return 2


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