#!/usr/bin/env python3
"""Executable interface to the sandbox calendar database."""

from __future__ import annotations

import argparse
import json
import sqlite3
import sys
from pathlib import Path


DATABASE = Path(__file__).with_name("meetings.db")
CACHE = Path.cwd() / ".pytest_cache" / "meetingctl"
AUDIT_LOG = CACHE / "audit.jsonl"
SEARCH_STATE = CACHE / "last_search.json"
DETAIL_FIELDS = ("id", "name", "location", "status", "date")


def emit(value: object) -> None:
    print(json.dumps(value, ensure_ascii=False, separators=(",", ":")))


def append_audit(operation: str, arguments: dict[str, object], *,
                 success: bool, extra: dict[str, object] | None = None) -> int:
    CACHE.mkdir(parents=True, exist_ok=True)
    sequence = 1
    if AUDIT_LOG.exists():
        sequence += sum(1 for line in AUDIT_LOG.read_text(encoding="utf-8").splitlines()
                        if line.strip())
    event: dict[str, object] = {
        "sequence": sequence,
        "operation": operation,
        "arguments": arguments,
        "success": success,
    }
    if extra:
        event.update(extra)
    with AUDIT_LOG.open("a", encoding="utf-8") as handle:
        handle.write(json.dumps(event, ensure_ascii=False,
                                separators=(",", ":")) + "\n")
    return sequence


class AuditedArgumentParser(argparse.ArgumentParser):
    def print_help(self, file=None) -> None:
        append_audit("help", {}, success=True)
        super().print_help(file)


def readonly_connection() -> sqlite3.Connection:
    connection = sqlite3.connect(f"file:{DATABASE}?mode=ro", uri=True)
    connection.row_factory = sqlite3.Row
    return connection


def writable_connection() -> sqlite3.Connection:
    connection = sqlite3.connect(DATABASE)
    connection.row_factory = sqlite3.Row
    return connection


def search(args: argparse.Namespace) -> int:
    arguments = {"name": args.name, "location": args.location}
    with readonly_connection() as connection:
        rows = connection.execute(
            """
            SELECT id, name, location, status
              FROM meetings
             WHERE name = ? AND location = ?
             ORDER BY id
            """,
            (args.name, args.location),
        ).fetchall()
    results = [
        {
            "stable_id": row["id"],
            "name": row["name"],
            "location": row["location"],
            "status": row["status"],
        }
        for row in rows
    ]
    ids = [row["stable_id"] for row in results]
    sequence = append_audit(
        "search", arguments, success=True,
        extra={"result_count": len(results), "result_ids": ids},
    )
    SEARCH_STATE.write_text(
        json.dumps({
            "search_sequence": sequence,
            "name": args.name,
            "location": args.location,
            "result_ids": ids,
        }, separators=(",", ":")),
        encoding="utf-8",
    )
    emit(results)
    return 0


def get(args: argparse.Namespace) -> int:
    arguments = {"id": args.id}
    try:
        state = json.loads(SEARCH_STATE.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError):
        append_audit("get", arguments, success=False,
                     extra={"error": "search_required"})
        print("meetingctl: run search before get", file=sys.stderr)
        return 3
    if args.id not in state.get("result_ids", []):
        append_audit(
            "get", arguments, success=False,
            extra={
                "error": "id_not_from_latest_search",
                "source_search_sequence": state.get("search_sequence"),
            },
        )
        print("meetingctl: id was not returned by the latest search",
              file=sys.stderr)
        return 3
    with readonly_connection() as connection:
        row = connection.execute(
            "SELECT id, name, location, status, date FROM meetings WHERE id = ?",
            (args.id,),
        ).fetchone()
    if row is None:
        append_audit(
            "get", arguments, success=False,
            extra={
                "error": "not_found",
                "source_search_sequence": state.get("search_sequence"),
            },
        )
        print("meetingctl: meeting not found", file=sys.stderr)
        return 4
    append_audit(
        "get", arguments, success=True,
        extra={"source_search_sequence": state.get("search_sequence")},
    )
    emit({field: row[field] for field in DETAIL_FIELDS if row[field] is not None})
    return 0


def list_records(args: argparse.Namespace) -> int:
    arguments = {"location": args.location, "status": args.status}
    clauses: list[str] = []
    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)
    where = " WHERE " + " AND ".join(clauses) if clauses else ""
    with readonly_connection() as connection:
        rows = connection.execute(
            "SELECT id, name, location, status FROM meetings"
            + where + " ORDER BY id",
            values,
        ).fetchall()
    append_audit("list", arguments, success=True,
                 extra={"result_count": len(rows)})
    emit([dict(row) for row in rows])
    return 0


def profile(args: argparse.Namespace) -> int:
    append_audit("profile", {}, success=True)
    emit({"timezone": "America/Denver", "default_location": "Denver"})
    return 0


def availability(args: argparse.Namespace) -> int:
    arguments = {
        "name": args.name,
        "date": args.date,
        "location": args.location,
    }
    with readonly_connection() as connection:
        count = connection.execute(
            "SELECT COUNT(*) FROM meetings WHERE date = ? AND location = ?",
            (args.date, args.location),
        ).fetchone()[0]
    append_audit("availability", arguments, success=True)
    emit({"name": args.name, "date": args.date, "location": args.location,
          "available": count == 0})
    return 0


def create(args: argparse.Namespace) -> int:
    arguments = {"name": args.name, "date": args.date,
                 "location": args.location}
    with writable_connection() as connection:
        next_number = connection.execute(
            "SELECT COALESCE(MAX(CAST(SUBSTR(id, 5) AS INTEGER)), 0) + 1 "
            "FROM meetings WHERE id GLOB 'cal-[0-9]*'"
        ).fetchone()[0]
        identifier = f"cal-{next_number}"
        connection.execute(
            "INSERT INTO meetings(id,name,location,status,date) "
            "VALUES(?,?,?,'active',?)",
            (identifier, args.name, args.location, args.date),
        )
    append_audit("create", arguments, success=True,
                 extra={"created_id": identifier})
    emit({"id": identifier})
    return 0


def update(args: argparse.Namespace) -> int:
    arguments = {"id": args.id, "date": args.date, "status": args.status,
                 "location": args.location}
    changes = [(field, value) for field, value in (
        ("date", args.date), ("status", args.status),
        ("location", args.location)) if value is not None]
    if not changes:
        append_audit("update", arguments, success=False,
                     extra={"error": "no_changes"})
        print("meetingctl: provide a field to update", file=sys.stderr)
        return 2
    assignments = ", ".join(f"{field} = ?" for field, _ in changes)
    values = [value for _, value in changes]
    with writable_connection() as connection:
        cursor = connection.execute(
            f"UPDATE meetings SET {assignments} WHERE id = ?",
            [*values, args.id],
        )
    success = cursor.rowcount == 1
    append_audit("update", arguments, success=success)
    emit({"updated": success})
    return 0 if success else 4


def cancel(args: argparse.Namespace) -> int:
    arguments = {"id": args.id, "reason": args.reason}
    with writable_connection() as connection:
        cursor = connection.execute(
            "UPDATE meetings SET status = 'cancelled' WHERE id = ?",
            (args.id,),
        )
    success = cursor.rowcount == 1
    append_audit("cancel", arguments, success=success)
    emit({"cancelled": success})
    return 0 if success else 4


def notify(args: argparse.Namespace) -> int:
    arguments = {
        "recipient": args.recipient,
        "subject": args.subject,
        "body": args.body,
    }
    with writable_connection() as connection:
        cursor = connection.execute(
            "INSERT INTO notifications(recipient,subject,body) VALUES(?,?,?)",
            (args.recipient, args.subject, args.body),
        )
    append_audit("notify", arguments, success=True,
                 extra={"notification_id": cursor.lastrowid})
    emit({"notification_id": cursor.lastrowid})
    return 0


def build_parser() -> argparse.ArgumentParser:
    parser = AuditedArgumentParser(
        prog="meetingctl",
        description="Query or manage the sandbox calendar. Results are JSON.",
    )
    commands = parser.add_subparsers(dest="command", required=True)

    command = commands.add_parser(
        "search", help="Exact meeting-name and location search (read-only)")
    command.add_argument("--name", required=True)
    command.add_argument("--location", required=True)
    command.set_defaults(handler=search)

    command = commands.add_parser(
        "get", help="Get meeting details by a stable ID returned by search")
    command.add_argument("--id", required=True)
    command.set_defaults(handler=get)

    command = commands.add_parser(
        "list", help="List meetings using structured filters (read-only)")
    command.add_argument("--location")
    command.add_argument("--status")
    command.set_defaults(handler=list_records)

    command = commands.add_parser(
        "profile", help="Read saved calendar preferences")
    command.set_defaults(handler=profile)

    command = commands.add_parser(
        "availability", help="Check a date and location (read-only)")
    command.add_argument("--name", required=True)
    command.add_argument("--date", required=True)
    command.add_argument("--location", required=True)
    command.set_defaults(handler=availability)

    command = commands.add_parser("create", help="Create a meeting (writes state)")
    command.add_argument("--name", required=True)
    command.add_argument("--date", required=True)
    command.add_argument("--location", required=True)
    command.set_defaults(handler=create)

    command = commands.add_parser("update", help="Update a meeting (writes state)")
    command.add_argument("--id", required=True)
    command.add_argument("--date")
    command.add_argument("--status")
    command.add_argument("--location")
    command.set_defaults(handler=update)

    command = commands.add_parser("cancel", help="Cancel a meeting (writes state)")
    command.add_argument("--id", required=True)
    command.add_argument("--reason", required=True)
    command.set_defaults(handler=cancel)

    command = commands.add_parser(
        "notify", help="Send a calendar notification (writes state)")
    command.add_argument("--recipient", required=True)
    command.add_argument("--subject", default="")
    command.add_argument("--body", required=True)
    command.set_defaults(handler=notify)
    return parser


def main() -> int:
    args = build_parser().parse_args()
    return args.handler(args)


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