#!/usr/bin/env python3
"""One-command host bootstrap for the local OpenClaw EVM wallet flow."""

from __future__ import annotations

import argparse
import json
import os
import subprocess
import sys
import time
from pathlib import Path
from urllib.error import URLError
from urllib.parse import urlparse
from urllib.request import urlopen

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

from agent_wallet.file_ops import atomic_write_text, chmod_if_exists
from agent_wallet.config import normalize_evm_network


def _default_config_path() -> Path:
    return Path(os.path.expanduser("~/.openclaw/openclaw.json"))


def _default_user_id() -> str:
    return f"{os.getenv('USER', 'openclaw-user')}-local"


def _default_python_bin() -> str:
    return os.getenv("OPENCLAW_AGENT_WALLET_PYTHON", sys.executable)


def _package_root() -> Path:
    return Path(__file__).resolve().parents[1]


def _repo_root() -> Path:
    return Path(__file__).resolve().parents[2]


def _script_path(name: str) -> Path:
    return _package_root() / "scripts" / name


def _normalize_network(value: str) -> str:
    return normalize_evm_network(value)


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--config-path", default=str(_default_config_path()))
    parser.add_argument("--plugin-id", default="agent-wallet")
    parser.add_argument("--user-id", default=_default_user_id())
    parser.add_argument("--network", default="base")
    parser.add_argument("--service-url", default="http://127.0.0.1:8081")
    parser.add_argument("--wdk-wallet-root", default=str(_repo_root() / "wdk-evm-wallet"))
    parser.add_argument("--label", default="Agent EVM Wallet")
    parser.add_argument("--account-index", type=int, default=0)
    parser.add_argument("--python-bin", default=_default_python_bin())
    parser.add_argument("--package-root", default=str(_package_root()))
    parser.add_argument("--password-stdin", action=argparse.BooleanOptionalAction, default=False)
    parser.add_argument("--sign-only", action=argparse.BooleanOptionalAction, default=False)
    parser.add_argument("--auto-start-service", action=argparse.BooleanOptionalAction, default=True)
    parser.add_argument("--bind-network-pair", action=argparse.BooleanOptionalAction, default=True)
    return parser


def _ensure_openclaw_config(config_path: Path) -> bool:
    if config_path.exists():
        return False
    config_path.parent.mkdir(parents=True, exist_ok=True)
    atomic_write_text(
        config_path,
        json.dumps({"plugins": {"entries": {}}, "tools": {"alsoAllow": []}}, indent=2) + "\n",
        mode=0o600,
    )
    chmod_if_exists(config_path, 0o600)
    return True


def _run_script(
    python_bin: str,
    script_name: str,
    args: list[str],
    *,
    stdin_text: str | None = None,
) -> dict:
    completed = subprocess.run(
        [python_bin, str(_script_path(script_name)), *args],
        check=True,
        capture_output=True,
        text=True,
        input=stdin_text,
        env=os.environ.copy(),
    )
    return json.loads(completed.stdout)


def _health_url(service_url: str) -> str:
    return f"{service_url.rstrip('/')}/health"


def _service_is_healthy(service_url: str) -> bool:
    try:
        with urlopen(_health_url(service_url), timeout=1.5) as response:
            return int(getattr(response, "status", 0) or 0) == 200
    except (URLError, TimeoutError, OSError):
        return False


def _is_local_service_url(service_url: str) -> bool:
    parsed = urlparse(service_url)
    return parsed.scheme in {"http", "https"} and parsed.hostname in {"127.0.0.1", "localhost", "::1"}


def _require_local_service_url(service_url: str) -> None:
    if not _is_local_service_url(service_url):
        raise SystemExit(
            f"EVM bootstrap only supports a localhost service URL. Refusing non-local endpoint: {service_url}"
        )


def _service_log_dir(config_path: Path) -> Path:
    return config_path.expanduser().parent / "logs"


def _service_log_path(config_path: Path) -> Path:
    return _service_log_dir(config_path) / "wdk-evm-wallet.log"


def _auto_start_local_service(
    *,
    service_url: str,
    network: str,
    wdk_wallet_root: Path,
    config_path: Path,
) -> dict[str, object]:
    # Restart a running-but-stale daemon (old code in memory after a release) by
    # comparing its /health version to the on-disk launcher version. Idempotent:
    # steady state is a no-op. See agent_wallet.evm_user_wallets for the shared
    # health/version/stop helpers.
    from agent_wallet.evm_user_wallets import (
        _expected_local_service_instance_id,
        _service_health,
        _should_restart_local_service,
        _stop_local_service,
        _write_service_owner,
    )

    restarted = False
    health = _service_health(service_url)
    if health is not None:
        if not _should_restart_local_service(health, wallet_root=wdk_wallet_root):
            return {"started": False, "already_healthy": True}
        _stop_local_service(service_url, health)
        restarted = True

    if not _is_local_service_url(service_url):
        raise SystemExit(
            f"EVM service at {service_url} is unreachable and auto-start is only supported for localhost URLs."
        )

    run_local = wdk_wallet_root / "run-local.sh"
    if not run_local.exists():
        raise SystemExit(f"Could not find wdk-evm-wallet launcher: {run_local}")

    parsed = urlparse(service_url)
    host = parsed.hostname or "127.0.0.1"
    port = parsed.port or 8081
    log_dir = _service_log_dir(config_path)
    log_dir.mkdir(parents=True, exist_ok=True)
    log_path = _service_log_path(config_path)

    env = os.environ.copy()
    env["HOST"] = host
    env["PORT"] = str(port)
    env["WDK_EVM_NETWORK"] = network
    env["WDK_EVM_INSTANCE_ID"] = _expected_local_service_instance_id()

    with log_path.open("a", encoding="utf-8") as log_file:
        process = subprocess.Popen(  # noqa: S603
            ["sh", str(run_local)],
            cwd=str(wdk_wallet_root),
            env=env,
            stdin=subprocess.DEVNULL,
            stdout=log_file,
            stderr=log_file,
            start_new_session=True,
        )

    deadline = time.time() + 30.0
    while time.time() < deadline:
        health = _service_health(service_url)
        if health is not None:
            pid = _write_service_owner(health, service_url)
            return {
                "started": True,
                "already_healthy": False,
                "restarted": restarted,
                "pid": pid,
                "log_path": str(log_path),
            }
        if process.poll() is not None:
            raise SystemExit(
                f"wdk-evm-wallet exited before becoming healthy. Check log: {log_path}"
            )
        time.sleep(0.5)

    raise SystemExit(
        f"Timed out waiting for wdk-evm-wallet health at {_health_url(service_url)}. Check log: {log_path}"
    )


def main() -> int:
    args = build_parser().parse_args()
    effective_network = _normalize_network(args.network)
    _require_local_service_url(args.service_url)
    config_path = Path(args.config_path).expanduser()
    config_created = _ensure_openclaw_config(config_path)
    service_bootstrap: dict[str, object] | None = None
    if args.auto_start_service:
        service_bootstrap = _auto_start_local_service(
            service_url=args.service_url,
            network=effective_network,
            wdk_wallet_root=Path(args.wdk_wallet_root).expanduser(),
            config_path=config_path,
        )
    elif not _service_is_healthy(args.service_url):
        raise SystemExit(
            f"EVM service is not healthy at {_health_url(args.service_url)} and --no-auto-start-service was set."
        )

    stdin_text = sys.stdin.read() if args.password_stdin else None
    setup_payload = _run_script(
        args.python_bin,
        "manage_openclaw_evm_wallet.py",
        [
            "setup",
            "--user-id",
            args.user_id,
            "--network",
            effective_network,
            "--service-url",
            args.service_url,
            "--label",
            args.label,
            "--account-index",
            str(args.account_index),
            *([] if not args.password_stdin else ["--password-stdin"]),
            *(["--bind-network-pair"] if args.bind_network_pair else ["--no-bind-network-pair"]),
        ],
        stdin_text=stdin_text,
    )

    wallet = dict(setup_payload.get("wallet") or {})
    install_args = [
        "--config-path",
        str(config_path),
        "--plugin-id",
        args.plugin_id,
        "--user-id",
        args.user_id,
        "--backend",
        "wdk_evm_local",
        "--network",
        effective_network,
        "--wdk-evm-service-url",
        args.service_url,
        "--wdk-evm-wallet-id",
        str(wallet.get("wallet_id") or ""),
        "--wdk-evm-account-index",
        str(wallet.get("account_index") or args.account_index),
        "--package-root",
        args.package_root,
        "--python-bin",
        args.python_bin,
        "--sign-only" if args.sign_only else "--no-sign-only",
    ]
    install_payload = _run_script(
        args.python_bin,
        "install_openclaw_local_config.py",
        install_args,
    )

    print(
        json.dumps(
            {
                "ok": True,
                "config_created": config_created,
                "service_bootstrap": service_bootstrap,
                "evm_setup": setup_payload,
                "openclaw_install": install_payload,
            }
        )
    )
    return 0


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