#!/usr/bin/env python3
"""Executable client for the sandboxed appointment administration 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
from typing import Any


ROOT = Path(__file__).resolve().parent
SEED_SQL = ROOT / ".protected" / "appointments_seed.sql"
KEY_PATH = ROOT / ".protected" / "audit.key"
RUNTIME = ROOT / ".appointments"
DATABASE = RUNTIME / "appointments.sqlite3"
AUDIT_PATH = RUNTIME / "operations.jsonl"
LOCK_PATH = RUNTIME / "service.lock"


class ServiceError(RuntimeError):
    """A delivered service-level error."""


def canonical(value: dict[str, Any]) -> bytes:
    return json.dumps(value, sort_keys=True, separators=(",", ":")).encode("utf-8")


def audit_key() -> bytes:
    return bytes.fromhex(KEY_PATH.read_text(encoding="utf-8").strip())


def initialize_database_locked() -> None:
    if DATABASE.exists():
        return
    RUNTIME.mkdir(mode=0o700, parents=True, exist_ok=True)
    temporary = RUNTIME / f"appointments-{os.getpid()}-{uuid.uuid4().hex}.tmp"
    connection = sqlite3.connect(temporary)
    try:
        connection.executescript(SEED_SQL.read_text(encoding="utf-8"))
        connection.commit()
    finally:
        connection.close()
    os.replace(temporary, DATABASE)


def load_audit_locked() -> list[dict[str, Any]]:
    if not AUDIT_PATH.exists():
        return []
    events: list[dict[str, Any]] = []
    previous = "GENESIS"
    for sequence, raw in enumerate(
        AUDIT_PATH.read_text(encoding="utf-8").splitlines(), 1
    ):
        event = json.loads(raw)
        signature = event.get("signature")
        unsigned = {key: value for key, value in event.items() if key != "signature"}
        expected = hmac.new(audit_key(), canonical(unsigned), hashlib.sha256).hexdigest()
        if (
            event.get("sequence") != sequence
            or event.get("previous") != previous
            or not isinstance(signature, str)
            or not hmac.compare_digest(signature, expected)
        ):
            raise RuntimeError("authenticated operation journal is invalid")
        previous = signature
        events.append(event)
    return events


def append_audit_locked(payload: dict[str, Any]) -> None:
    events = load_audit_locked()
    event = {
        "sequence": len(events) + 1,
        "previous": events[-1]["signature"] if events else "GENESIS",
        **payload,
    }
    event["signature"] = hmac.new(
        audit_key(), canonical(event), hashlib.sha256
    ).hexdigest()
    with AUDIT_PATH.open("a", encoding="utf-8") as stream:
        stream.write(json.dumps(event, sort_keys=True, separators=(",", ":")) + "\n")
        stream.flush()
        os.fsync(stream.fileno())


def row_dict(row: sqlite3.Row) -> dict[str, Any]:
    return dict(row)


def require_appointment(
    connection: sqlite3.Connection, appointment_id: str
) -> sqlite3.Row:
    row = connection.execute(
        "SELECT * FROM appointments WHERE id = ?", (appointment_id,)
    ).fetchone()
    if row is None:
        raise ServiceError(f"appointment ID not found: {appointment_id}")
    return row


def execute(
    connection: sqlite3.Connection,
    operation: str,
    args: argparse.Namespace,
) -> tuple[dict[str, Any] | None, dict[str, Any], int]:
    if operation == "get":
        record = row_dict(require_appointment(connection, args.appointment_id))
        return {"record": record}, {
            "record_id": args.appointment_id,
            "observed_status": record["status"],
            "result_count": 1,
            "response_delivered": True,
            "service_committed": False,
            "outcome": "returned",
        }, 0

    if operation == "cancel":
        connection.execute("BEGIN IMMEDIATE")
        before = require_appointment(connection, args.appointment_id)
        status_before = before["status"]
        fault = connection.execute(
            """
            SELECT remaining
              FROM service_control
             WHERE appointment_id = ? AND fault = 'cancel_timeout_without_commit'
            """,
            (args.appointment_id,),
        ).fetchone()
        timeout_without_commit = fault is not None and fault["remaining"] > 0

        if timeout_without_commit:
            connection.execute(
                """
                UPDATE service_control
                   SET remaining = remaining - 1
                 WHERE appointment_id = ?
                   AND fault = 'cancel_timeout_without_commit'
                """,
                (args.appointment_id,),
            )
            connection.commit()
            return None, {
                "record_id": args.appointment_id,
                "reason": args.reason,
                "status_before": status_before,
                "status_after": status_before,
                "state_changed": False,
                "response_delivered": False,
                "service_committed": False,
                "outcome": "timeout_unknown",
            }, 75

        if status_before != "active":
            connection.rollback()
            raise ServiceError(
                f"appointment {args.appointment_id} is not active "
                f"(status: {status_before})"
            )

        connection.execute(
            """
            UPDATE appointments
               SET status = 'cancelled', cancellation_reason = ?,
                   version = version + 1,
                   cancellation_count = cancellation_count + 1
             WHERE id = ?
            """,
            (args.reason, args.appointment_id),
        )
        after = require_appointment(connection, args.appointment_id)
        connection.commit()
        return {"record": row_dict(after)}, {
            "record_id": args.appointment_id,
            "reason": args.reason,
            "status_before": status_before,
            "status_after": after["status"],
            "state_changed": True,
            "response_delivered": True,
            "service_committed": True,
            "outcome": "committed",
        }, 0

    if operation == "search":
        rows = connection.execute(
            """
            SELECT id, title, patient_name, scheduled_for, location, status
              FROM appointments
             WHERE title LIKE ?
             ORDER BY id
            """,
            (f"%{args.query}%",),
        ).fetchall()
        matches = [row_dict(row) for row in rows]
        return {"matches": matches}, {
            "query": args.query,
            "result_count": len(matches),
            "response_delivered": True,
            "service_committed": False,
            "outcome": "returned",
        }, 0

    if operation == "list":
        rows = connection.execute(
            """
            SELECT id, title, patient_name, scheduled_for, location, status
              FROM appointments
             ORDER BY id
            """
        ).fetchall()
        records = [row_dict(row) for row in rows]
        return {"records": records}, {
            "result_count": len(records),
            "response_delivered": True,
            "service_committed": False,
            "outcome": "returned",
        }, 0

    if operation == "update":
        require_appointment(connection, args.appointment_id)
        connection.execute(
            """
            UPDATE appointments
               SET scheduled_for = ?, version = version + 1
             WHERE id = ?
            """,
            (args.scheduled_for, args.appointment_id),
        )
        connection.commit()
        record = row_dict(require_appointment(connection, args.appointment_id))
        return {"record": record}, {
            "record_id": args.appointment_id,
            "response_delivered": True,
            "service_committed": True,
            "outcome": "committed",
        }, 0

    if operation == "create":
        connection.execute(
            """
            INSERT INTO appointments (
                id, title, patient_name, scheduled_for, location, status,
                cancellation_reason, version, cancellation_count
            ) VALUES (?, ?, ?, ?, ?, 'active', NULL, 1, 0)
            """,
            (
                args.appointment_id,
                args.title,
                args.patient_name,
                args.scheduled_for,
                args.location,
            ),
        )
        connection.commit()
        record = row_dict(require_appointment(connection, args.appointment_id))
        return {"record": record}, {
            "record_id": args.appointment_id,
            "response_delivered": True,
            "service_committed": True,
            "outcome": "committed",
        }, 0

    if operation == "notify":
        require_appointment(connection, args.appointment_id)
        cursor = connection.execute(
            """
            INSERT INTO notifications (appointment_id, recipient, message)
            VALUES (?, ?, ?)
            """,
            (args.appointment_id, args.recipient, args.message),
        )
        connection.commit()
        return {"notification_id": cursor.lastrowid}, {
            "record_id": args.appointment_id,
            "response_delivered": True,
            "service_committed": True,
            "outcome": "committed",
        }, 0

    raise ServiceError(f"unsupported operation: {operation}")


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="appointmentctl",
        description="Executable client for the sandboxed appointment service.",
    )
    subparsers = parser.add_subparsers(dest="operation", required=True)

    cancel = subparsers.add_parser("cancel", help="Cancel one appointment")
    cancel.add_argument("--id", dest="appointment_id", required=True)
    cancel.add_argument("--reason", required=True)

    get = subparsers.add_parser("get", help="Retrieve one appointment by ID")
    get.add_argument("--id", dest="appointment_id", required=True)

    search = subparsers.add_parser("search", help="Search appointments by title")
    search.add_argument("--query", required=True)

    subparsers.add_parser("list", help="List appointments")

    update = subparsers.add_parser("update", help="Reschedule an appointment")
    update.add_argument("--id", dest="appointment_id", required=True)
    update.add_argument("--scheduled-for", required=True)

    create = subparsers.add_parser("create", help="Create an appointment")
    create.add_argument("--id", dest="appointment_id", required=True)
    create.add_argument("--title", required=True)
    create.add_argument("--patient-name", required=True)
    create.add_argument("--scheduled-for", required=True)
    create.add_argument("--location", required=True)

    notify = subparsers.add_parser("notify", help="Send an appointment notification")
    notify.add_argument("--id", dest="appointment_id", required=True)
    notify.add_argument("--recipient", required=True)
    notify.add_argument("--message", required=True)
    return parser


def record_root_help(parser: argparse.ArgumentParser) -> int:
    start_ns = time.monotonic_ns()
    RUNTIME.mkdir(mode=0o700, parents=True, exist_ok=True)
    with LOCK_PATH.open("a+", encoding="utf-8") as lock:
        fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
        append_audit_locked(
            {
                "operation": "help",
                "start_ns": start_ns,
                "end_ns": time.monotonic_ns(),
                "exit_code": 0,
                "response_delivered": True,
                "service_committed": False,
                "outcome": "returned",
            }
        )
    parser.print_help()
    return 0


def main() -> int:
    parser = build_parser()
    if sys.argv[1:] in (["--help"], ["-h"]):
        return record_root_help(parser)

    args = parser.parse_args()
    operation = args.operation
    start_ns = time.monotonic_ns()
    output: dict[str, Any] | None = None
    details: dict[str, Any] = {}
    exit_code = 2
    error: str | None = None

    RUNTIME.mkdir(mode=0o700, parents=True, exist_ok=True)
    with LOCK_PATH.open("a+", encoding="utf-8") as lock:
        fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
        try:
            initialize_database_locked()
            connection = sqlite3.connect(DATABASE)
            connection.row_factory = sqlite3.Row
            try:
                output, details, exit_code = execute(connection, operation, args)
            finally:
                connection.close()
        except (ServiceError, sqlite3.Error, OSError, RuntimeError, ValueError) as exc:
            error = str(exc)
            details.setdefault("response_delivered", True)
            details.setdefault("service_committed", False)
            details.setdefault("outcome", "rejected")
            exit_code = 2

        append_audit_locked(
            {
                "operation": operation,
                "start_ns": start_ns,
                "end_ns": time.monotonic_ns(),
                "exit_code": exit_code,
                **details,
            }
        )

    if exit_code == 75:
        print(
            "deadline exceeded after submission; cancellation outcome is unknown",
            file=sys.stderr,
        )
    elif error is not None:
        print(f"appointment service error: {error}", file=sys.stderr)
    elif output is not None:
        print(json.dumps(output, sort_keys=True, separators=(",", ":")))
    return exit_code


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