from __future__ import annotations

import argparse
import importlib
from importlib import metadata
import os
from pathlib import Path
import subprocess
import sys

from . import resolve_cf_pipeline_v2_executable

try:
    from cf_cli.registry import register_command
except ImportError:  # pragma: no cover - optional dependency
    register_command = None


DIRECT_ENGINE_ALLOW_ENV = "CF_ALLOW_DIRECT_ENGINE"
UNIFIED_ENGINE_REGISTER_ENV = "CF_ENABLE_UNIFIED_ENGINE_COMMAND"


def _env_enabled(name: str) -> bool:
    raw = os.environ.get(name, "")
    return raw.strip().lower() in {"1", "true", "yes", "on"}


def _direct_engine_allowed() -> bool:
    return _env_enabled(DIRECT_ENGINE_ALLOW_ENV)


def _policy_error_message() -> str:
    return (
        "Direct engine invocation is blocked by policy. "
        "Use 'python -m cf_ontology state activate -> state emit-event -> state run-loop'. "
        f"For explicit override only, set {DIRECT_ENGINE_ALLOW_ENV}=1."
    )


def _find_default_exe() -> str:
    return resolve_cf_pipeline_v2_executable()


def _add_engine_args(parser: argparse.ArgumentParser) -> None:
    parser.add_argument("--pipeline", required=True, help="Path to pipeline JSON-LD")
    parser.add_argument("--steps", action="append", help="Path to steps JSON-LD (repeatable)")
    parser.add_argument(
        "--plugins",
        action="append",
        help="Directory containing step plugins (optional)",
    )
    parser.add_argument(
        "--engine-threads",
        "--threads",
        dest="engine_threads",
        type=int,
        default=0,
        help="Engine worker threads (0=auto)",
    )
    parser.add_argument(
        "--max-total-threads",
        type=int,
        default=None,
        help="Global thread budget cap (default=engine threads)",
    )
    parser.add_argument(
        "--default-step-threads",
        type=int,
        default=None,
        help="Default per-step internal thread budget",
    )
    parser.add_argument(
        "--respect-step-parallelism",
        nargs="?",
        const="true",
        default=None,
        choices=["true", "false"],
        help="Honor step parallelism metadata (default=true)",
    )
    parser.add_argument(
        "--deterministic",
        nargs="?",
        const="true",
        default=None,
        choices=["true", "false"],
        help="Enable deterministic mode (conservative threading)",
    )
    parser.add_argument(
        "--interval",
        type=float,
        default=0.0,
        help="Seconds between repeated runs (0=single run)",
    )
    parser.add_argument(
        "--iterations",
        type=int,
        default=1,
        help="Number of iterations when interval > 0",
    )
    parser.add_argument(
        "--duration",
        type=float,
        default=0.0,
        help="Total duration in seconds (overrides iterations)",
    )
    parser.add_argument("--debug", action="store_true", help="Enable debug checklist output")
    parser.add_argument(
        "--exe",
        default=_find_default_exe(),
        help="Path to cf_pipeline_v2 executable",
    )


def _discover_plugin_dirs() -> list[str]:
    plugin_dirs: list[str] = []
    seen_pkgs: set[str] = set()
    seen_dirs: set[str] = set()
    try:
        entry_points = metadata.entry_points(group="cogniflow.steps")
    except TypeError:
        entry_points = metadata.entry_points().get("cogniflow.steps", [])
    for ep in entry_points:
        module_name = ep.value.split(":")[0]
        pkg_name = module_name.split(".")[0]
        if pkg_name in seen_pkgs:
            continue
        seen_pkgs.add(pkg_name)
        try:
            module = importlib.import_module(pkg_name)
        except Exception:
            continue
        module_file = getattr(module, "__file__", None)
        if not module_file:
            continue
        pkg_path = Path(module_file).resolve().parent
        bin_dir = pkg_path / "bin"
        if bin_dir.exists():
            resolved = str(bin_dir.resolve())
            if resolved not in seen_dirs:
                seen_dirs.add(resolved)
                plugin_dirs.append(resolved)
    return plugin_dirs


def _run_from_args(args: argparse.Namespace) -> int:
    if not _direct_engine_allowed():
        print(_policy_error_message(), file=sys.stderr)
        return 2

    plugin_dirs = args.plugins or _discover_plugin_dirs()
    if not plugin_dirs:
        print(
            "No plugin directories found. Proceeding without --plugins; "
            "the pipeline header must provide plugin paths.",
            file=sys.stderr,
        )

    cmd = [args.exe, "--pipeline", args.pipeline]
    if args.steps:
        for step_path in args.steps:
            cmd.extend(["--steps", step_path])
    for plugin_dir in plugin_dirs:
        cmd.extend(["--plugins", plugin_dir])
    if args.engine_threads:
        cmd.extend(["--engine-threads", str(args.engine_threads)])
    if args.max_total_threads is not None:
        cmd.extend(["--max-total-threads", str(args.max_total_threads)])
    if args.default_step_threads is not None:
        cmd.extend(["--default-step-threads", str(args.default_step_threads)])
    if args.respect_step_parallelism is not None:
        cmd.extend(["--respect-step-parallelism", str(args.respect_step_parallelism)])
    if args.deterministic is not None:
        cmd.extend(["--deterministic", str(args.deterministic)])
    if args.interval:
        cmd.extend(["--interval", str(args.interval)])
    if args.interval and args.iterations:
        cmd.extend(["--iterations", str(args.iterations)])
    if args.duration:
        cmd.extend(["--duration", str(args.duration)])
    if args.debug:
        cmd.append("--debug")

    try:
        proc = subprocess.run(cmd, check=False)
    except FileNotFoundError:
        print(f"Executable not found: {args.exe}", file=sys.stderr)
        return 2
    return proc.returncode


def _register_cf_cli() -> None:
    if register_command is None:
        return
    if not _env_enabled(UNIFIED_ENGINE_REGISTER_ENV):
        return

    def _add_parser(subparsers: argparse._SubParsersAction) -> argparse.ArgumentParser:
        parser = subparsers.add_parser("engine", help="Run the pipeline engine.")
        _add_engine_args(parser)
        parser.set_defaults(_cf_handler=_run_from_args)
        return parser

    register_command(
        "pipeline",
        "engine",
        _add_parser,
        group_help="Pipeline tools",
        command_help="Run the pipeline engine",
    )


def _register_optional_pipeline_commands() -> None:
    if register_command is None:
        return
    for module_name in (
        "cf_pipeline_cert.cli",
        "cf_pipeline_report.cli",
        "cf_pipeline_report.cli_plugin",
    ):
        try:
            importlib.import_module(module_name)
        except ImportError:
            continue


def main() -> int:
    parser = argparse.ArgumentParser(description="Run a pipeline using the C++ v2 backend.")
    _add_engine_args(parser)
    args = parser.parse_args()
    return _run_from_args(args)


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


_register_cf_cli()
_register_optional_pipeline_commands()
