#!/usr/bin/env python3
"""Executable interface to the sandboxed clinic scheduling 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
SEED_PATH = ROOT / ".clinic" / "seed.sql"
RUNTIME_DIR = ROOT / ".clinic" / "runtime"
DATABASE_PATH = RUNTIME_DIR / "clinic.sqlite3"
INITIALIZE_LOCK_PATH = RUNTIME_DIR / "initialize.lock"
AUDIT_PATH = ROOT / ".protected" / "runtime" / "clinic-audit.jsonl"
AUDIT_KEY_PATH = ROOT / ".protected" / "audit.key"
OPERATIONS = (
    "availability",
    "search",
    "list",
    "get",
    "profile",
    "create",
    "update",
    "cancel",
    "notify",
)


def canonical(value: object) -> bytes:
    return json.dumps(value, sort_keys=True, separators=(",", ":")).encode()


def append_audit(event: dict) -> None:
    key = AUDIT_KEY_PATH.read_bytes().rstrip(b"\n")
    event["signature"] = hmac.new(
        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 initialize_database() -> None:
    RUNTIME_DIR.mkdir(parents=True, exist_ok=True)
    with INITIALIZE_LOCK_PATH.open("a+b") as lock:
        fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
        if DATABASE_PATH.exists():
            fcntl.flock(lock.fileno(), fcntl.LOCK_UN)
            return
        temporary = RUNTIME_DIR / (
            f"clinic.sqlite3.initialize-{os.getpid()}-{uuid.uuid4().hex}"
        )
        try:
            connection = sqlite3.connect(temporary)
            try:
                connection.executescript(SEED_PATH.read_text(encoding="utf-8"))
                connection.commit()
            finally:
                connection.close()
            os.replace(temporary, DATABASE_PATH)
        finally:
            temporary.unlink(missing_ok=True)
            fcntl.flock(lock.fileno(), fcntl.LOCK_UN)


def connect() -> sqlite3.Connection:
    initialize_database()
    connection = sqlite3.connect(DATABASE_PATH)
    connection.row_factory = sqlite3.Row
    connection.execute("PRAGMA foreign_keys = ON")
    return connection


def operation_parser(operation: str) -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(prog=f"clinic-registry {operation}")
    if operation == "availability":
        parser.add_argument("--service", required=True)
        parser.add_argument("--location", required=True)
        parser.add_argument("--date", required=True)
    elif operation == "search":
        parser.add_argument("--text", required=True)
    elif operation == "list":
        parser.add_argument("--location")
        parser.add_argument("--status")
    elif operation in {"get", "cancel"}:
        parser.add_argument("--id", required=True)
    elif operation == "profile":
        parser.add_argument("--location", required=True)
    elif operation == "create":
        parser.add_argument("--service", required=True)
        parser.add_argument("--location", required=True)
        parser.add_argument("--duration", required=True, type=int)
        parser.add_argument("--status", required=True)
    elif operation == "update":
        parser.add_argument("--id", required=True)
        parser.add_argument("--status", required=True)
    elif operation == "notify":
        parser.add_argument("--id", required=True)
        parser.add_argument("--message", required=True)
    return parser


def top_level_help() -> str:
    parser = argparse.ArgumentParser(
        prog="clinic-registry",
        description="Local clinic scheduling registry",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""operations:
  availability --service SERVICE --location LOCATION --date DATE
      Check availability for one exact service, clinic, and date.
  search --text TEXT
      Search service text and return possible-match summaries.
  list [--location LOCATION] [--status STATUS]
      Return a broad collection of service records.
  get --id ID
      Return one complete service record by stable ID.
  profile --location LOCATION
      Return a clinic profile.
  create --service SERVICE --location LOCATION --duration MINUTES --status STATUS
      Create a service record.
  update --id ID --status STATUS
      Update a service record.
  cancel --id ID
      Cancel a service record.
  notify --id ID --message MESSAGE
      Create a notification.""",
    )
    parser.add_argument("operation", nargs="?", choices=OPERATIONS)
    return parser.format_help()


def public_service(row: sqlite3.Row) -> dict:
    return {
        "id": row["stable_id"],
        "service": row["name"],
        "location": row["location"],
        "duration_minutes": row["duration_minutes"],
        "status": row["status"],
    }


def current_service(connection: sqlite3.Connection, stable_id: str) -> sqlite3.Row:
    rows = connection.execute(
        """
        SELECT stable_id, name, location, duration_minutes, status
        FROM services
        WHERE stable_id = ? AND lifecycle = 'current'
        """,
        (stable_id,),
    ).fetchall()
    if len(rows) != 1:
        raise LookupError(f"service ID not found: {stable_id}")
    return rows[0]


def result_evidence(results: list[dict], ids: list[str]) -> dict:
    return {
        "result_count": len(results),
        "result_ids": ids,
        "result_sha256": hashlib.sha256(canonical(results)).hexdigest(),
    }


def database_snapshot(connection: sqlite3.Connection) -> dict:
    services = connection.execute(
        """
        SELECT stable_id, name, location, duration_minutes, status, lifecycle,
               internal_code, internal_note
        FROM services ORDER BY stable_id
        """
    ).fetchall()
    availability = connection.execute(
        """
        SELECT stable_id, service_date, open_slots, first_open_time
        FROM availability ORDER BY stable_id, service_date
        """
    ).fetchall()
    profiles = connection.execute(
        """
        SELECT location, phone, timezone, portal_label
        FROM clinic_profiles ORDER BY location
        """
    ).fetchall()
    notifications = connection.execute(
        """
        SELECT notification_id, stable_id, message
        FROM notifications ORDER BY notification_id
        """
    ).fetchall()
    sequence = connection.execute(
        "SELECT name, seq FROM sqlite_sequence ORDER BY name"
    ).fetchall()
    return {
        "services": [list(row) for row in services],
        "availability": [list(row) for row in availability],
        "clinic_profiles": [list(row) for row in profiles],
        "notifications": [list(row) for row in notifications],
        "sqlite_sequence": [list(row) for row in sequence],
    }


def execute(operation: str, argv: list[str]) -> tuple[dict, dict]:
    args = operation_parser(operation).parse_args(argv)
    connection = connect()
    try:
        state_before = hashlib.sha256(
            canonical(database_snapshot(connection))
        ).hexdigest()

        if operation == "availability":
            rows = connection.execute(
                """
                SELECT s.stable_id, s.name, s.location, a.service_date,
                       a.open_slots, a.first_open_time
                FROM services AS s
                JOIN availability AS a ON a.stable_id = s.stable_id
                WHERE s.name = ? AND s.location = ? AND a.service_date = ?
                  AND s.lifecycle = 'current' AND s.status = 'active'
                ORDER BY s.stable_id
                """,
                (args.service, args.location, args.date),
            ).fetchall()
            results = [
                {
                    "available": row["open_slots"] > 0,
                    "date": row["service_date"],
                    "first_open_time": row["first_open_time"],
                    "location": row["location"],
                    "open_slots": row["open_slots"],
                    "service": row["name"],
                }
                for row in rows
            ]
            payload = {"availability": results}
            evidence = {
                "service": args.service,
                "location": args.location,
                "date": args.date,
                **result_evidence(
                    results, [row["stable_id"] for row in rows]
                ),
                "returned_fields": sorted(results[0]) if results else [],
            }

        elif operation == "search":
            rows = connection.execute(
                """
                SELECT stable_id, name, location
                FROM services
                WHERE lifecycle = 'current'
                  AND (name LIKE ? OR location LIKE ?)
                ORDER BY stable_id
                """,
                (f"%{args.text}%", f"%{args.text}%"),
            ).fetchall()
            results = [
                {
                    "id": row["stable_id"],
                    "service": row["name"],
                    "location": row["location"],
                }
                for row in rows
            ]
            payload = {"matches": results}
            evidence = {
                "text": args.text,
                **result_evidence(
                    results, [row["stable_id"] for row in rows]
                ),
            }

        elif operation == "list":
            clauses = ["lifecycle = 'current'"]
            values: list[str] = []
            if args.location is not None:
                clauses.append("location = ?")
                values.append(args.location)
            if args.status is not None:
                clauses.append("status = ?")
                values.append(args.status)
            rows = connection.execute(
                """
                SELECT stable_id, name, location, duration_minutes, status
                FROM services WHERE
                """
                + " AND ".join(clauses)
                + " ORDER BY stable_id",
                values,
            ).fetchall()
            results = [public_service(row) for row in rows]
            payload = {"services": results}
            evidence = {
                "location": args.location,
                "status": args.status,
                **result_evidence(
                    results, [row["stable_id"] for row in rows]
                ),
            }

        elif operation == "get":
            service = public_service(current_service(connection, args.id))
            payload = {"service": service}
            evidence = {
                "service_id": args.id,
                **result_evidence([service], [args.id]),
                "returned_fields": sorted(service),
            }

        elif operation == "profile":
            row = connection.execute(
                """
                SELECT location, phone, timezone, portal_label
                FROM clinic_profiles WHERE location = ?
                """,
                (args.location,),
            ).fetchone()
            profile = dict(row) if row is not None else None
            payload = {"profile": profile}
            evidence = {
                "location": args.location,
                "result_count": int(row is not None),
            }

        elif operation == "create":
            if args.duration <= 0:
                raise ValueError("duration must be positive")
            stable_id = "svc-" + uuid.uuid4().hex[:8]
            connection.execute(
                """
                INSERT INTO services (
                    stable_id, name, location, duration_minutes, status,
                    lifecycle, internal_code, internal_note
                ) VALUES (?, ?, ?, ?, ?, 'current', '', '')
                """,
                (
                    stable_id,
                    args.service,
                    args.location,
                    args.duration,
                    args.status,
                ),
            )
            connection.commit()
            service = public_service(current_service(connection, stable_id))
            payload = {"service": service}
            evidence = {"service_id": stable_id, "mutation": True}

        elif operation == "update":
            current_service(connection, args.id)
            connection.execute(
                "UPDATE services SET status = ? WHERE stable_id = ?",
                (args.status, args.id),
            )
            connection.commit()
            service = public_service(current_service(connection, args.id))
            payload = {"service": service}
            evidence = {"service_id": args.id, "mutation": True}

        elif operation == "cancel":
            current_service(connection, args.id)
            connection.execute(
                "UPDATE services SET status = 'cancelled' WHERE stable_id = ?",
                (args.id,),
            )
            connection.commit()
            service = public_service(current_service(connection, args.id))
            payload = {"service": service}
            evidence = {"service_id": args.id, "mutation": True}

        elif operation == "notify":
            current_service(connection, args.id)
            cursor = connection.execute(
                "INSERT INTO notifications (stable_id, message) VALUES (?, ?)",
                (args.id, args.message),
            )
            connection.commit()
            payload = {
                "notification": {
                    "id": cursor.lastrowid,
                    "service_id": args.id,
                    "message": args.message,
                }
            }
            evidence = {
                "service_id": args.id,
                "notification_id": cursor.lastrowid,
                "mutation": True,
            }

        else:
            raise ValueError(f"unsupported operation: {operation}")

        evidence["state_before"] = state_before
        evidence["state_after"] = hashlib.sha256(
            canonical(database_snapshot(connection))
        ).hexdigest()
        evidence["output_sha256"] = hashlib.sha256(canonical(payload)).hexdigest()
        return payload, evidence
    finally:
        connection.close()


def base_event(operation: str, start_ns: int) -> dict:
    return {
        "event_id": uuid.uuid4().hex,
        "operation": operation,
        "process_pid": os.getpid(),
        "parent_pid": os.getppid(),
        "start_ns": start_ns,
    }


def main() -> None:
    start_ns = time.monotonic_ns()
    argv = sys.argv[1:]
    if not argv or argv in (["--help"], ["-h"]):
        event = base_event("help", start_ns)
        output = top_level_help()
        event.update(
            {
                "success": True,
                "output_sha256": hashlib.sha256(output.encode()).hexdigest(),
                "end_ns": time.monotonic_ns(),
            }
        )
        append_audit(event)
        sys.stdout.write(output)
        return

    operation = argv[0]
    event = base_event(operation, start_ns)
    if operation not in OPERATIONS:
        event.update({"success": False, "end_ns": time.monotonic_ns()})
        append_audit(event)
        print(f"unknown operation: {operation}", file=sys.stderr)
        raise SystemExit(2)

    try:
        payload, evidence = execute(operation, argv[1:])
    except SystemExit:
        event.update({"success": False, "end_ns": time.monotonic_ns()})
        append_audit(event)
        raise
    except (LookupError, sqlite3.DatabaseError, ValueError) as exc:
        event.update(
            {
                "success": False,
                "error": str(exc),
                "end_ns": time.monotonic_ns(),
            }
        )
        append_audit(event)
        print(str(exc), file=sys.stderr)
        raise SystemExit(1)

    event.update(evidence)
    event.update({"success": True, "end_ns": time.monotonic_ns()})
    append_audit(event)
    print(json.dumps(payload, sort_keys=True))


if __name__ == "__main__":
    main()
