"""AcTrail daemon and trace helpers for performance benchmarks."""

from __future__ import annotations

import configparser
import subprocess
import time
from pathlib import Path

from lib.paths import resolve
from lib.paths import resolve_command
from lib.trace_store import read_trace_diagnostics


def clean_operator(repo: Path, config: configparser.ConfigParser, operator: Path) -> None:
    ctl = resolve_command(repo, config["runner"]["actrailctl"])
    run_checked([str(ctl), "clean", "--config", str(operator)], repo)


def start_daemon(repo: Path, config: configparser.ConfigParser, operator: Path) -> None:
    daemon = resolve_command(repo, config["runner"]["actraild"])
    run_checked([str(daemon), "start", "--config", str(operator)], repo)
    run_checked([str(daemon), "status", "--config", str(operator)], repo)


def stop_daemon(repo: Path, config: configparser.ConfigParser, operator: Path, check: bool) -> None:
    daemon = resolve_command(repo, config["runner"]["actraild"])
    result = subprocess.run(
        [str(daemon), "stop", "--config", str(operator)],
        cwd=repo,
        text=True,
        capture_output=True,
        check=False,
    )
    if check and result.returncode != 0:
        raise RuntimeError(f"failed to stop actraild:\n{result.stdout}{result.stderr}")


def wait_for_completed_summary(
    repo: Path,
    config: configparser.ConfigParser,
    operator: Path,
    trace_id: int,
) -> str:
    viewer = resolve_command(repo, config["runner"]["actrailviewer"])
    attempts = config["runner"].getint("drain_attempts")
    sleep_seconds = config["runner"].getfloat("drain_sleep_seconds")
    summary = ""
    last_lock_error = ""
    for _ in range(attempts):
        try:
            summary = run_checked(
                [
                    str(viewer),
                    "summary",
                    "--config",
                    str(operator),
                    "--trace-id",
                    str(trace_id),
                ],
                repo,
            )
        except RuntimeError as error:
            if "database is locked" not in str(error):
                raise
            last_lock_error = str(error)
            time.sleep(sleep_seconds)
            continue
        if "state=Exited" in summary or "state=Completed" in summary:
            return summary
        time.sleep(sleep_seconds)
    if last_lock_error and not summary:
        raise RuntimeError(last_lock_error)
    return summary


def read_diagnostics(operator: Path, trace_id: int) -> str:
    return read_trace_diagnostics(operator, trace_id)


def diagnostics_only_bootstrap_gap(diagnostics: str) -> bool:
    rows = [line for line in diagnostics.splitlines() if line.startswith("diag-")]
    return bool(rows) and all("bootstrap_gap" in row or "BootstrapGap" in row for row in rows)


def operator_config(repo: Path, config: configparser.ConfigParser, mode: str) -> Path | None:
    if mode == "baseline":
        return None
    if mode in {"daemon-idle", "observed-ebpf-core"}:
        return resolve(repo, config["runner"]["basic_operator_config"])
    if mode == "observed-ebpf-payload":
        return resolve(repo, config["runner"]["full_operator_config"])
    if mode == "observed-seccomp-agent":
        return resolve(repo, config["runner"]["llm_operator_config"])
    raise RuntimeError(f"unknown mode {mode}")


def run_checked(command: list[str], cwd: Path) -> str:
    result = subprocess.run(command, cwd=cwd, text=True, capture_output=True, check=False)
    if result.returncode != 0:
        raise RuntimeError(
            f"command failed exit={result.returncode}: {' '.join(command)}\n"
            f"{result.stdout}{result.stderr}"
        )
    return result.stdout + result.stderr