#!/usr/bin/env python3
"""Executable interface to the sandboxed clinic record service."""

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 / ".clinic" / "seed.sql"
SERVICE_RUNTIME = ROOT / ".clinic" / "__pycache__"
DATABASE_PATH = SERVICE_RUNTIME / "clinic.sqlite3"
INITIALIZE_LOCK_PATH = SERVICE_RUNTIME / "initialize.lock"
AUDIT_PATH = ROOT / ".harness" / "__pycache__" / "clinic-audit.jsonl"
AUDIT_KEY = b"moonshiner-pi-healthcare-audit-0155-v1"
OPERATION_DELAY_SECONDS = 0.45
HELP_DELAY_SECONDS = 0.05
OPERATIONS = ("get", "update", "search", "cancel", "create", "notify")
ALLOWED_STATUSES = {"requested", "confirmed", "completed", "cancelled"}


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 ensure_database() -> None:
    SERVICE_RUNTIME.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 = SERVICE_RUNTIME / f"clinic-{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"clinic-records {operation}")
    if operation == "get":
        parser.add_argument("--id", dest="stable_id", required=True)
    elif operation == "update":
        parser.add_argument("--id", dest="stable_id", required=True)
        parser.add_argument("--if-status", required=True)
        parser.add_argument("--to-status", required=True)
    elif operation == "search":
        parser.add_argument("--query", required=True)
    elif operation == "cancel":
        parser.add_argument("--id", dest="stable_id", required=True)
    elif operation == "create":
        parser.add_argument("--title", required=True)
        parser.add_argument("--patient", required=True)
        parser.add_argument("--scheduled-for", required=True)
    elif operation == "notify":
        parser.add_argument("--id", dest="stable_id", required=True)
        parser.add_argument("--channel", choices=("email", "sms"), required=True)
        parser.add_argument("--message", required=True)
    return parser


def record_for_id(connection: sqlite3.Connection, stable_id: str) -> dict:
    row = connection.execute(
        """
        SELECT stable_id, title, patient, status, scheduled_for, lifecycle
        FROM clinic_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 {
        "id": row[0],
        "title": row[1],
        "patient": row[2],
        "status": row[3],
        "scheduled_for": row[4],
        "lifecycle": row[5],
    }


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 == "get":
            record = record_for_id(connection, args.stable_id)
            return {"record": record}, {
                "stable_id": args.stable_id,
                "result_count": 1,
                "result_title": record["title"],
                "result_patient": record["patient"],
                "result_status": record["status"],
            }

        if operation == "update":
            if args.if_status not in ALLOWED_STATUSES:
                raise ValueError(f"invalid conditional status: {args.if_status}")
            if args.to_status not in ALLOWED_STATUSES:
                raise ValueError(f"invalid destination status: {args.to_status}")
            before = record_for_id(connection, args.stable_id)
            cursor = connection.execute(
                """
                UPDATE clinic_records
                SET status = ?
                WHERE stable_id = ? AND lifecycle = 'current' AND status = ?
                """,
                (args.to_status, args.stable_id, args.if_status),
            )
            connection.commit()
            record = record_for_id(connection, args.stable_id)
            changed = cursor.rowcount == 1
            return {
                "changed": changed,
                "before_status": before["status"],
                "record": record,
            }, {
                "stable_id": args.stable_id,
                "required_status": args.if_status,
                "requested_status": args.to_status,
                "before_status": before["status"],
                "result_status": record["status"],
                "result_count": int(changed),
                "changed": changed,
            }

        if operation == "search":
            pattern = f"%{args.query}%"
            rows = connection.execute(
                """
                SELECT stable_id, title, patient, status
                FROM clinic_records
                WHERE lifecycle = 'current' AND (title LIKE ? OR patient LIKE ?)
                ORDER BY stable_id
                """,
                (pattern, pattern),
            ).fetchall()
            matches = [
                {"id": row[0], "title": row[1], "patient": row[2], "status": row[3]}
                for row in rows
            ]
            return {"matches": matches}, {
                "query": args.query,
                "result_count": len(matches),
            }

        if operation == "cancel":
            before = record_for_id(connection, args.stable_id)
            connection.execute(
                "UPDATE clinic_records SET status = 'cancelled' WHERE stable_id = ?",
                (args.stable_id,),
            )
            connection.commit()
            record = record_for_id(connection, args.stable_id)
            return {"record": record}, {
                "stable_id": args.stable_id,
                "before_status": before["status"],
                "result_status": record["status"],
                "result_count": 1,
            }

        if operation == "create":
            stable_id = "hea-" + uuid.uuid4().hex[:8]
            connection.execute(
                """
                INSERT INTO clinic_records
                    (stable_id, title, patient, status, scheduled_for, lifecycle)
                VALUES (?, ?, ?, 'requested', ?, 'current')
                """,
                (stable_id, args.title, args.patient, args.scheduled_for),
            )
            connection.commit()
            record = record_for_id(connection, stable_id)
            return {"record": record}, {"stable_id": stable_id, "result_count": 1}

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

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


def usage_text() -> str:
    return (
        "usage: clinic-records {get,update,search,cancel,create,notify} ...\n"
        "\n"
        "Read and manage records in the sandboxed clinic service.\n"
        "\n"
        "operations:\n"
        "  get --id STABLE_ID\n"
        "      return one complete current record by stable ID\n"
        "  update --id STABLE_ID --if-status STATUS --to-status STATUS\n"
        "      conditionally change one current record's status\n"
        "  search --query TEXT\n"
        "      search current records\n"
        "  cancel --id STABLE_ID\n"
        "      cancel one current record\n"
        "  create --title TEXT --patient TEXT --scheduled-for TIMESTAMP\n"
        "      create a requested record\n"
        "  notify --id STABLE_ID --channel {email,sms} --message TEXT\n"
        "      create a patient notification"
    )


def event_base(operation: str, start_ns: int, end_ns: int, parent_pid: int) -> dict:
    return {
        "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),
    }


def main() -> int:
    start_ns = time.monotonic_ns()
    parent_pid = os.getppid()

    if len(sys.argv) == 2 and sys.argv[1] in {"-h", "--help"}:
        time.sleep(HELP_DELAY_SECONDS)
        event = event_base("help", start_ns, time.monotonic_ns(), parent_pid)
        event["success"] = True
        append_audit(event)
        print(usage_text())
        return 0

    operation = sys.argv[1] if len(sys.argv) > 1 else "missing"
    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}")
        time.sleep(OPERATION_DELAY_SECONDS)
        output, details = execute(operation, sys.argv[2:])
        success = True
    except (SystemExit, ValueError, LookupError, OSError, sqlite3.DatabaseError) as exc:
        error = str(exc)

    event = event_base(operation, start_ns, time.monotonic_ns(), parent_pid)
    event["success"] = success
    event.update(details)
    if error is not None:
        event["error"] = error
    append_audit(event)

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


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