"""Command-line interface for cf_ontology package operations."""

from __future__ import annotations

import argparse
import json
import os
from pathlib import Path
from typing import Any, Dict, Optional

from .ingest import ingest_rdf_documents, rebuild_semantics_db
from .ontology_manager import OntologyManager
from .package_discovery import ingest_installed_step_packages
from .pipeline_event_gatekeeper import emit_pipeline_event
from .pipeline_state_runtime import (
    activate_pipeline_state,
    run_event_loop,
    set_pipeline_state,
)
from .pipeline_id import derive_pipeline_id_from_document
from .signature import generate_signature_header


def _apply_semantics_env(semantics_dir: Optional[Path], enable_steps: bool) -> None:
    if semantics_dir:
        os.environ["CF_SEMANTICS_DIR"] = str(semantics_dir)
    if enable_steps:
        os.environ["CF_ENABLE_STEP_PACKAGES"] = "1"


def _parse_semantics_dir(value: Optional[str]) -> Optional[Path]:
    if not value:
        return None
    return Path(value).expanduser().resolve()


def _load_json(path: Path) -> Dict[str, Any]:
    return json.loads(path.read_text(encoding="utf-8"))


def _write_json(path: Optional[str], payload: Dict[str, Any], compact: bool) -> None:
    if path:
        out_path = Path(path)
        out_path.parent.mkdir(parents=True, exist_ok=True)
        with out_path.open("w", encoding="utf-8") as fh:
            if compact:
                json.dump(payload, fh, separators=(",", ":"), sort_keys=False)
            else:
                json.dump(payload, fh, indent=2, sort_keys=False)
                fh.write("\n")
        return
    if compact:
        print(json.dumps(payload, separators=(",", ":"), sort_keys=False))
    else:
        print(json.dumps(payload, indent=2, sort_keys=False))


def _write_text(path: Optional[str], payload: str) -> None:
    if path:
        out_path = Path(path)
        out_path.parent.mkdir(parents=True, exist_ok=True)
        out_path.write_text(payload, encoding="utf-8")
        return
    print(payload, end="")


def _cmd_init(args: argparse.Namespace) -> int:
    semantics_dir = _parse_semantics_dir(args.semantics_dir)
    _apply_semantics_env(semantics_dir, bool(args.enable_step_packages))
    if args.rebuild:
        db_path = rebuild_semantics_db(semantics_dir=semantics_dir)
    else:
        om = OntologyManager()
        db_path = om.ontology_db_path
    if args.print_path:
        print(str(db_path))
    return 0


def _cmd_ingest(args: argparse.Namespace) -> int:
    semantics_dir = _parse_semantics_dir(args.semantics_dir)
    _apply_semantics_env(semantics_dir, bool(args.enable_step_packages))

    if args.installed_steps:
        ingest_installed_step_packages(semantics_dir=semantics_dir, force=bool(args.force))
        return 0

    paths = [Path(p) for p in (args.paths or [])]
    ingest_rdf_documents(
        paths,
        semantics_dir=semantics_dir,
        graph_type=str(args.graph_type),
        package=str(args.package),
    )
    return 0


def _cmd_export(args: argparse.Namespace) -> int:
    semantics_dir = _parse_semantics_dir(args.semantics_dir)
    _apply_semantics_env(semantics_dir, bool(args.enable_step_packages))
    om = OntologyManager()
    payload = om.get_graph_nquads()
    _write_text(args.out, payload)
    return 0


def _cmd_pipeline_commit(args: argparse.Namespace) -> int:
    semantics_dir = _parse_semantics_dir(args.semantics_dir)
    _apply_semantics_env(semantics_dir, bool(args.enable_step_packages))
    om = OntologyManager()
    rev = om.commit_pipeline(
        args.pipeline_id,
        Path(args.document),
        user=args.user,
        message=args.message,
    )
    print(str(rev))
    return 0


def _cmd_pipeline_get(args: argparse.Namespace) -> int:
    semantics_dir = _parse_semantics_dir(args.semantics_dir)
    _apply_semantics_env(semantics_dir, bool(args.enable_step_packages))
    om = OntologyManager()
    payload = om.get_pipeline_revision_nquads(args.pipeline_id, rev=args.rev)
    _write_text(args.out, payload)
    return 0


def _cmd_pipeline_id(args: argparse.Namespace) -> int:
    pipeline_id = derive_pipeline_id_from_document(Path(args.document))
    print(str(pipeline_id))
    return 0


def _cmd_state_activate(args: argparse.Namespace) -> int:
    semantics_dir = _parse_semantics_dir(args.semantics_dir)
    _apply_semantics_env(semantics_dir, bool(args.enable_step_packages))
    activate_pipeline_state(
        args.pipeline_id,
        desired_state=str(args.desired_state),
        concurrency_limit=int(args.concurrency_limit),
        priority=int(args.priority),
    )
    return 0


def _cmd_state_set(args: argparse.Namespace) -> int:
    semantics_dir = _parse_semantics_dir(args.semantics_dir)
    _apply_semantics_env(semantics_dir, bool(args.enable_step_packages))
    set_pipeline_state(args.pipeline_id, str(args.desired_state))
    return 0


def _cmd_state_emit_event(args: argparse.Namespace) -> int:
    semantics_dir = _parse_semantics_dir(args.semantics_dir)
    _apply_semantics_env(semantics_dir, bool(args.enable_step_packages))

    payload: Any = {}
    if args.payload_json:
        payload = json.loads(str(args.payload_json))
    elif args.payload_file:
        payload = _load_json(Path(args.payload_file))

    result = emit_pipeline_event(
        pipeline_id=args.pipeline_id,
        event_type=str(args.event_type),
        source=str(args.source),
        payload=payload,
        dedupe_key=args.dedupe_key,
        source_event_id=args.source_event_id,
    )
    _write_json(
        args.out,
        {
            "event_id": result.event_id,
            "pipeline_id": result.pipeline_id,
            "dedupe_key": result.dedupe_key,
            "created": result.created,
        },
        compact=bool(args.compact),
    )
    return 0


def _cmd_state_run(args: argparse.Namespace) -> int:
    semantics_dir = _parse_semantics_dir(args.semantics_dir)
    _apply_semantics_env(semantics_dir, bool(args.enable_step_packages))
    return int(
        run_event_loop(
            args.pipeline_id,
            worker_id=args.worker_id,
            poll_interval=float(args.poll_interval),
            once=bool(args.once),
            invocation_document=args.invocation_document,
        )
    )


def _cmd_siggen(args: argparse.Namespace) -> int:
    semantics_dir = _parse_semantics_dir(args.semantics_dir)
    _apply_semantics_env(semantics_dir, bool(args.enable_step_packages))
    documents = [Path(p) for p in (args.documents or [])] if args.documents else None
    generate_signature_header(
        documents=documents,
        out_path=Path(args.out),
        only_prefix=args.only,
        semantics_dir=semantics_dir,
        scratch=bool(args.scratch),
    )
    return 0


def _build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(prog="cf_ontology")
    sub = parser.add_subparsers(dest="command", required=True)

    def add_semantics_args(p: argparse.ArgumentParser) -> None:
        p.add_argument("--semantics-dir", default=None, help="Target semantics directory.")
        p.add_argument(
            "--enable-step-packages",
            action="store_true",
            help="Enable step package discovery (CF_ENABLE_STEP_PACKAGES=1).",
        )

    init = sub.add_parser("init", help="Initialize the semantics DB using cf_ontology.")
    add_semantics_args(init)
    init.add_argument("--rebuild", action="store_true", help="Delete and rebuild the DB.")
    init.add_argument("--print-path", action="store_true", help="Print the DB path on success.")
    init.set_defaults(func=_cmd_init)

    ingest = sub.add_parser("ingest", help="Ingest RDF documents into the semantics DB.")
    add_semantics_args(ingest)
    source = ingest.add_mutually_exclusive_group(required=True)
    source.add_argument("--paths", nargs="+", help="One or more RDF document files (.nq) to ingest.")
    source.add_argument(
        "--installed-steps",
        action="store_true",
        help="Discover and ingest installed step packages.",
    )
    ingest.add_argument("--graph-type", default="custom", help="Graph type label.")
    ingest.add_argument("--package", default="custom", help="Package label.")
    ingest.add_argument("--force", action="store_true", help="Force re-ingest when hashes match.")
    ingest.set_defaults(func=_cmd_ingest)

    export = sub.add_parser("export", help="Export semantics as N-Quads.")
    add_semantics_args(export)
    export.add_argument("--out", default=None, help="Output file path (default: stdout).")
    export.set_defaults(func=_cmd_export)

    pipeline = sub.add_parser("pipeline", help="Pipeline revision operations.")
    pipe_sub = pipeline.add_subparsers(dest="pipeline_cmd", required=True)

    commit = pipe_sub.add_parser("commit", help="Commit a pipeline revision.")
    add_semantics_args(commit)
    commit.add_argument("--pipeline-id", required=True, help="Stable pipeline id.")
    commit.add_argument(
        "--document",
        required=True,
        help="Pipeline RDF document file (.nq).",
    )
    commit.add_argument("--user", default=None, help="Author/username.")
    commit.add_argument("--message", default=None, help="Commit message.")
    commit.set_defaults(func=_cmd_pipeline_commit)

    get = pipe_sub.add_parser("get", help="Fetch a pipeline revision.")
    add_semantics_args(get)
    get.add_argument("--pipeline-id", required=True, help="Stable pipeline id.")
    get.add_argument("--rev", type=int, default=None, help="Revision number (default: current).")
    get.add_argument("--out", default=None, help="Output file path (default: stdout).")
    get.set_defaults(func=_cmd_pipeline_get)

    pid = pipe_sub.add_parser("id", help="Derive pipeline id from an RDF document.")
    pid.add_argument(
        "--document",
        required=True,
        help="Pipeline RDF document file (.nq).",
    )
    pid.set_defaults(func=_cmd_pipeline_id)

    state = sub.add_parser("state", help="Pipeline runtime commands (canonical start: emit-event then run-loop).")
    state_sub = state.add_subparsers(dest="state_cmd", required=True)

    activate = state_sub.add_parser("activate", help="Create/update pipeline runtime state.")
    add_semantics_args(activate)
    activate.add_argument("--pipeline-id", required=True, help="Stable pipeline id.")
    activate.add_argument(
        "--desired-state",
        default="enabled",
        choices=["enabled", "paused", "sleep", "disabled"],
        help="Desired runtime state.",
    )
    activate.add_argument("--concurrency-limit", type=int, default=1, help="Max concurrent runs.")
    activate.add_argument("--priority", type=int, default=0, help="Scheduling priority.")
    activate.set_defaults(func=_cmd_state_activate)

    set_state = state_sub.add_parser("set", help="Update desired pipeline state.")
    add_semantics_args(set_state)
    set_state.add_argument("--pipeline-id", required=True, help="Stable pipeline id.")
    set_state.add_argument(
        "--desired-state",
        required=True,
        choices=["enabled", "paused", "sleep", "disabled"],
        help="Desired runtime state.",
    )
    set_state.set_defaults(func=_cmd_state_set)

    emit_event = state_sub.add_parser("emit-event", help="Persist a trigger event (canonical run start input).")
    add_semantics_args(emit_event)
    emit_event.add_argument("--pipeline-id", required=True, help="Stable pipeline id.")
    emit_event.add_argument("--event-type", required=True, help="Runtime event type label.")
    emit_event.add_argument("--source", default="manual", help="Event source label.")
    emit_event.add_argument("--dedupe-key", default=None, help="Optional idempotency key.")
    emit_event.add_argument(
        "--source-event-id",
        default=None,
        help="Optional external event identity.",
    )
    payload_group = emit_event.add_mutually_exclusive_group()
    payload_group.add_argument("--payload-json", default=None, help="Inline JSON payload.")
    payload_group.add_argument("--payload-file", default=None, help="Path to JSON payload file.")
    emit_event.add_argument("--out", default=None, help="Output file path (default: stdout).")
    emit_event.add_argument("--compact", action="store_true", help="Write compact JSON.")
    emit_event.set_defaults(func=_cmd_state_emit_event)

    run_loop = state_sub.add_parser("run-loop", help="Consume pending events and execute pipeline runs.")
    add_semantics_args(run_loop)
    run_loop.add_argument("--pipeline-id", required=True, help="Stable pipeline id.")
    run_loop.add_argument("--worker-id", default=None, help="Worker identifier.")
    run_loop.add_argument(
        "--poll-interval",
        type=float,
        default=1.0,
        help="Polling interval seconds.",
    )
    run_loop.add_argument(
        "--invocation-document",
        default=None,
        help="Optional invocation RDF document file (.nq) with entry-point overrides.",
    )
    run_loop.add_argument("--once", action="store_true", help="Process at most one run.")
    run_loop.set_defaults(func=_cmd_state_run)

    siggen = sub.add_parser("siggen", help="Generate signature header from DuckDB semantics.")
    add_semantics_args(siggen)
    siggen.add_argument("--document", dest="documents", action="append", help="Step RDF document file (.nq) to ingest (repeatable).")
    siggen.add_argument("--out", required=True, help="Output header path.")
    siggen.add_argument("--only", default=None, help="Only include step IRIs with this prefix.")
    siggen.add_argument(
        "--scratch",
        action="store_true",
        help="Use a temporary semantics directory (no global DB writes).",
    )
    siggen.set_defaults(func=_cmd_siggen)

    return parser


def main(argv: Optional[list[str]] = None) -> int:
    """Parse CLI arguments and dispatch to the selected command handler."""
    parser = _build_parser()
    args = parser.parse_args(argv)
    return int(args.func(args) or 0)


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