#!/usr/bin/env python3
"""Read-only command-line interface for the stockroom inventory database."""

from __future__ import annotations

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


ROOT = Path(__file__).resolve().parents[1]
DEFAULT_DATABASE = ROOT / "data" / "inventory.db"


def parser() -> argparse.ArgumentParser:
    command = argparse.ArgumentParser(
        prog="inventory",
        description="Search inventory keys and retrieve complete inventory records.",
    )
    subcommands = command.add_subparsers(dest="command", required=True)

    search = subcommands.add_parser(
        "search", help="find stable IDs by exact item name and exact location"
    )
    search.add_argument("--name", required=True)
    search.add_argument("--location", required=True)

    get = subcommands.add_parser("get", help="retrieve one complete record by stable ID")
    get.add_argument("--id", dest="stable_id", required=True)
    return command


def database_path() -> Path:
    override = os.environ.get("INVENTORY_DATABASE")
    return Path(override) if override else DEFAULT_DATABASE


def audit(event: dict[str, Any]) -> None:
    destination = os.environ.get("INVENTORY_AUDIT_LOG")
    if not destination:
        return
    path = Path(destination)
    path.parent.mkdir(parents=True, exist_ok=True)
    with path.open("a", encoding="utf-8") as handle:
        fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
        handle.write(json.dumps(event, sort_keys=True, separators=(",", ":")) + "\n")
        handle.flush()
        os.fsync(handle.fileno())
        fcntl.flock(handle.fileno(), fcntl.LOCK_UN)


def wait_for_test_peers(command: str) -> None:
    """Rendezvous concurrent verifier calls when the test environment requests it."""
    destination = os.environ.get("INVENTORY_TEST_BARRIER")
    expected_text = os.environ.get(
        f"INVENTORY_TEST_{command.upper()}_PEERS", "1"
    )
    expected = int(expected_text)
    if not destination or expected <= 1:
        return

    directory = Path(destination)
    directory.mkdir(parents=True, exist_ok=True)
    (directory / f"{command}-{os.getpid()}").touch()
    deadline = time.monotonic() + 4
    while len(tuple(directory.glob(f"{command}-*"))) < expected:
        if time.monotonic() >= deadline:
            raise TimeoutError(f"timed out waiting for concurrent {command} peer")
        time.sleep(0.01)


def query_search(connection: sqlite3.Connection, name: str, location: str) -> dict[str, Any]:
    rows = connection.execute(
        "SELECT stable_id FROM inventory WHERE name = ? AND location = ? ORDER BY stable_id",
        (name, location),
    ).fetchall()
    return {"matches": [{"stable_id": row[0]} for row in rows]}


def query_get(connection: sqlite3.Connection, stable_id: str) -> dict[str, Any]:
    row = connection.execute(
        """
        SELECT stable_id, name, location, record_date, status, bin_code,
               reorder_point, notes
          FROM inventory
         WHERE stable_id = ?
        """,
        (stable_id,),
    ).fetchone()
    if row is None:
        return {"record": None}
    keys = (
        "stable_id",
        "name",
        "location",
        "date",
        "status",
        "bin_code",
        "reorder_point",
        "notes",
    )
    return {"record": dict(zip(keys, row, strict=True))}


def main() -> int:
    arguments = parser().parse_args()
    started_ns = time.monotonic_ns()
    event: dict[str, Any] = {
        "command": arguments.command,
        "pid": os.getpid(),
        "started_ns": started_ns,
    }
    try:
        wait_for_test_peers(arguments.command)
        uri = f"file:{database_path().resolve()}?mode=ro"
        with sqlite3.connect(uri, uri=True) as connection:
            if arguments.command == "search":
                event["name"] = arguments.name
                event["location"] = arguments.location
                result = query_search(connection, arguments.name, arguments.location)
            else:
                event["stable_id"] = arguments.stable_id
                result = query_get(connection, arguments.stable_id)

        delay_ms = int(os.environ.get("INVENTORY_OPERATION_DELAY_MS", "75"))
        if delay_ms > 0:
            time.sleep(delay_ms / 1000)
        event["ok"] = True
        print(json.dumps(result, sort_keys=True, separators=(",", ":")))
        return 0
    except (OSError, sqlite3.Error, ValueError) as error:
        event["ok"] = False
        event["error_type"] = type(error).__name__
        print(f"inventory: {error}", file=sys.stderr)
        return 1
    finally:
        event["finished_ns"] = time.monotonic_ns()
        audit(event)


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