#!/usr/bin/env python3
"""Small local support-case desk used by the dependency-planning exercise."""

from __future__ import annotations

import argparse
import fcntl
import json
import os
from pathlib import Path
import sys
import time
from typing import Any


ROOT = Path(__file__).resolve().parent
STORE = ROOT / ".casework"
RECORDS = STORE / "records.json"
AUDIT = STORE / "audit.jsonl"
NOTIFICATIONS = STORE / "notifications.json"
READ_LATENCY_SECONDS = 0.45


def load_json(path: Path) -> Any:
    with path.open(encoding="utf-8") as handle:
        return json.load(handle)


def append_audit(event: dict[str, Any]) -> None:
    line = json.dumps(event, sort_keys=True, separators=(",", ":")) + "\n"
    with AUDIT.open("a", encoding="utf-8") as handle:
        fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
        handle.write(line)
        handle.flush()
        os.fsync(handle.fileno())
        fcntl.flock(handle.fileno(), fcntl.LOCK_UN)


def read_operation(
    operation: str,
    details: dict[str, Any],
    producer,
    result_details=lambda _result: {},
) -> Any:
    started = time.monotonic_ns()
    time.sleep(READ_LATENCY_SECONDS)
    result = producer()
    finished = time.monotonic_ns()
    append_audit(
        {
            "op": operation,
            "pid": os.getpid(),
            "started_ns": started,
            "finished_ns": finished,
            **details,
            **result_details(result),
        }
    )
    return result


def command_search(args: argparse.Namespace) -> int:
    def find_matches() -> list[dict[str, str]]:
        records = load_json(RECORDS)
        return [
            {
                "id": record["id"],
                "name": record["name"],
                "location": record["location"],
            }
            for record in records
            if record["name"].casefold() == args.query.casefold()
            and record["location"].casefold() == args.location.casefold()
        ]

    matches = read_operation(
        "search",
        {"query": args.query, "location": args.location},
        find_matches,
        lambda result: {"match_ids": [record["id"] for record in result]},
    )
    event = {
        "query": args.query,
        "location": args.location,
        "matches": matches,
    }
    print(json.dumps(event, sort_keys=True))
    return 0


def command_get(args: argparse.Namespace) -> int:
    def fetch_record() -> dict[str, str] | None:
        return next(
            (record for record in load_json(RECORDS) if record["id"] == args.id),
            None,
        )

    record = read_operation("get", {"id": args.id}, fetch_record)
    if record is None:
        print(json.dumps({"error": "record not found", "id": args.id}))
        return 4
    print(json.dumps(record, sort_keys=True))
    return 0


def command_update(args: argparse.Namespace) -> int:
    started = time.monotonic_ns()
    with RECORDS.open("r+", encoding="utf-8") as handle:
        fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
        records = json.load(handle)
        record = next((item for item in records if item["id"] == args.id), None)
        if record is None:
            fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
            print(json.dumps({"error": "record not found", "id": args.id}))
            return 4
        before = record["status"]
        record["status"] = args.status
        handle.seek(0)
        json.dump(records, handle, indent=2)
        handle.write("\n")
        handle.truncate()
        handle.flush()
        os.fsync(handle.fileno())
        fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
    finished = time.monotonic_ns()
    append_audit(
        {
            "op": "update",
            "pid": os.getpid(),
            "started_ns": started,
            "finished_ns": finished,
            "id": args.id,
            "before_status": before,
            "after_status": args.status,
        }
    )
    print(json.dumps(record, sort_keys=True))
    return 0


def command_cancel(args: argparse.Namespace) -> int:
    args.status = "cancelled"
    return command_update(args)


def command_notify(args: argparse.Namespace) -> int:
    started = time.monotonic_ns()
    with NOTIFICATIONS.open("r+", encoding="utf-8") as handle:
        fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
        notifications = json.load(handle)
        notifications.append({"record_id": args.record_id, "message": args.message})
        handle.seek(0)
        json.dump(notifications, handle, indent=2)
        handle.write("\n")
        handle.truncate()
        handle.flush()
        os.fsync(handle.fileno())
        fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
    append_audit(
        {
            "op": "notify",
            "pid": os.getpid(),
            "started_ns": started,
            "finished_ns": time.monotonic_ns(),
            "record_id": args.record_id,
        }
    )
    print(json.dumps({"sent": True, "record_id": args.record_id}))
    return 0


def parser() -> argparse.ArgumentParser:
    root = argparse.ArgumentParser(
        prog="casework",
        description="Query and update the workspace-local support case desk.",
    )
    commands = root.add_subparsers(dest="command", required=True)

    search = commands.add_parser("search", help="exact-name search scoped to a location")
    search.add_argument("--query", required=True)
    search.add_argument("--location", required=True)
    search.set_defaults(handler=command_search)

    get = commands.add_parser("get", help="retrieve one complete record by stable ID")
    get.add_argument("--id", required=True)
    get.set_defaults(handler=command_get)

    update = commands.add_parser("update", help="change one record status")
    update.add_argument("--id", required=True)
    update.add_argument("--status", required=True)
    update.set_defaults(handler=command_update)

    cancel = commands.add_parser("cancel", help="cancel one record")
    cancel.add_argument("--id", required=True)
    cancel.set_defaults(handler=command_cancel)

    notify = commands.add_parser("notify", help="send a case notification")
    notify.add_argument("--record-id", required=True)
    notify.add_argument("--message", required=True)
    notify.set_defaults(handler=command_notify)
    return root


def main() -> int:
    args = parser().parse_args()
    try:
        return args.handler(args)
    except (json.JSONDecodeError, OSError) as error:
        print(f"casework: local store error: {error}", file=sys.stderr)
        return 70


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