#!/usr/bin/env python3
"""Executable client for the sandboxed facilities 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" / "facilities_seed.sql"
KEY_PATH = ROOT / ".protected" / "audit.key"
RUNTIME = ROOT / ".facilities"
DATABASE = RUNTIME / "facilities.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"facilities-{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_request(connection: sqlite3.Connection, request_id: str) -> sqlite3.Row:
    row = connection.execute(
        "SELECT * FROM requests WHERE id = ?", (request_id,)
    ).fetchone()
    if row is None:
        raise ServiceError(f"request ID not found: {request_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_request(connection, args.request_id))
        return {"record": record}, {
            "record_id": args.request_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_request(connection, args.request_id)
        status_before = before["status"]
        if status_before != "active":
            connection.rollback()
            raise ServiceError(
                f"request {args.request_id} is not active (status: {status_before})"
            )

        fault = connection.execute(
            """
            SELECT remaining
              FROM service_control
             WHERE request_id = ? AND fault = 'cancel_deadline_after_commit'
            """,
            (args.request_id,),
        ).fetchone()
        lose_response = fault is not None and fault["remaining"] > 0

        connection.execute(
            """
            UPDATE requests
               SET status = 'cancelled', cancellation_reason = ?,
                   version = version + 1,
                   cancellation_count = cancellation_count + 1
             WHERE id = ?
            """,
            (args.reason, args.request_id),
        )
        after = require_request(connection, args.request_id)

        if lose_response:
            connection.execute(
                """
                UPDATE service_control
                   SET remaining = remaining - 1
                 WHERE request_id = ?
                   AND fault = 'cancel_deadline_after_commit'
                """,
                (args.request_id,),
            )
        connection.commit()

        details = {
            "record_id": args.request_id,
            "reason": args.reason,
            "status_before": status_before,
            "status_after": after["status"],
            "state_changed": True,
            "response_delivered": not lose_response,
            "service_committed": True,
            "outcome": "deadline_after_commit" if lose_response else "committed",
        }
        if lose_response:
            return None, details, 75
        return {"record": row_dict(after)}, details, 0

    if operation == "search":
        rows = connection.execute(
            """
            SELECT id, name, location, scheduled_for, status
              FROM requests
             WHERE name 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, name, location, scheduled_for, status
              FROM requests
             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_request(connection, args.request_id)
        connection.execute(
            """
            UPDATE requests
               SET scheduled_for = ?, version = version + 1
             WHERE id = ?
            """,
            (args.scheduled_for, args.request_id),
        )
        connection.commit()
        record = row_dict(require_request(connection, args.request_id))
        return {"record": record}, {
            "record_id": args.request_id,
            "response_delivered": True,
            "service_committed": True,
            "outcome": "committed",
        }, 0

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

    if operation == "notify":
        require_request(connection, args.request_id)
        cursor = connection.execute(
            """
            INSERT INTO notifications (request_id, recipient, message)
            VALUES (?, ?, ?)
            """,
            (args.request_id, args.recipient, args.message),
        )
        connection.commit()
        return {"notification_id": cursor.lastrowid}, {
            "record_id": args.request_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="facilityctl",
        description="Executable client for the sandboxed facilities service.",
    )
    subparsers = parser.add_subparsers(dest="operation", required=True)

    cancel = subparsers.add_parser("cancel", help="Cancel one facilities request")
    cancel.add_argument("--id", dest="request_id", required=True)
    cancel.add_argument("--reason", required=True)

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

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

    subparsers.add_parser("list", help="List facilities requests")

    update = subparsers.add_parser("update", help="Reschedule a request")
    update.add_argument("--id", dest="request_id", required=True)
    update.add_argument("--scheduled-for", required=True)

    create = subparsers.add_parser("create", help="Create a facilities request")
    create.add_argument("--id", dest="request_id", required=True)
    create.add_argument("--name", required=True)
    create.add_argument("--location", required=True)
    create.add_argument("--scheduled-for", required=True)

    notify = subparsers.add_parser("notify", help="Send a request notification")
    notify.add_argument("--id", dest="request_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"facilities 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())
