from __future__ import annotations

import os
from pathlib import Path
from typing import Callable, List, Optional

from armorer.catalog import resolve_playbook
from armorer.openclaw_config import resolve_openclaw_registered_install_path
from armorer.registry import AppMetadata, AppRegistry


def looks_like_openclaw_runtime_dir(path: Path) -> bool:
    target = Path(path)
    return target.exists() and (
        (target / "docker-compose.yml").exists()
        or (target / "docker-compose.yaml").exists()
        or (target / ".openclaw" / "openclaw.json").exists()
    )


def resolve_openclaw_runtime_dir(install_dir: Path, compose_subdir: str = ".") -> Optional[Path]:
    root = Path(install_dir)
    candidates = [root.resolve()]
    if compose_subdir not in {"", "."}:
        candidates.insert(0, (root / compose_subdir).resolve())
    for candidate in candidates:
        if looks_like_openclaw_runtime_dir(candidate):
            return candidate
    return None


def fallback_openclaw_default_app(
    registry: AppRegistry,
    agent: str,
    *,
    infer_agent_id: Callable[[str], str],
    persist_registry_app_metadata: Callable[..., None],
) -> Optional[AppMetadata]:
    requested = str(agent or "").strip()
    if requested != "openclaw" or infer_agent_id(requested) != "openclaw":
        return None

    playbook = resolve_playbook("openclaw")
    compose_subdir = str(getattr(playbook, "compose_subdir", ".") or ".").strip() or "."
    monitor_targets = [str(x) for x in (getattr(playbook, "monitor_targets", []) or []) if str(x).strip()]
    monitoring_mode = str(getattr(playbook, "monitoring_mode", "hybrid") or "hybrid").strip().lower() or "hybrid"
    run_commands = [
        {
            "label": str(cmd.label),
            "service": str(cmd.service),
            "command": str(cmd.command),
            "category": str(cmd.category or "run"),
            "description": str(cmd.description or ""),
        }
        for cmd in (getattr(playbook, "run_commands", []) or [])
        if str(getattr(cmd, "command", "") or "").strip() and str(getattr(cmd, "service", "") or "").strip()
    ]

    candidates: List[Path] = []
    seen: set[str] = set()

    def _add(path: Path) -> None:
        key = str(path)
        if key in seen:
            return
        seen.add(key)
        candidates.append(path)

    host_root = str(os.getenv("HOST_PROJECT_ROOT") or "").strip()
    if host_root:
        _add((Path(host_root) / "installed" / "openclaw").resolve())
    _add((Path.cwd() / "installed" / "openclaw").resolve())
    _add(Path("/app/installed") / "openclaw")
    _add((Path.home() / "workplace" / "SecureNeural" / "Armorer" / "installed" / "openclaw").resolve())

    for install_root in candidates:
        compose_dir = resolve_openclaw_runtime_dir(install_root, compose_subdir)
        if compose_dir is not None:
            app_meta = AppMetadata(
                name="openclaw",
                install_path=str(compose_dir),
                agent_id="openclaw",
                monitor_targets=monitor_targets,
                monitoring_mode=monitoring_mode,
                run_commands=run_commands,
            )
            persist_registry_app_metadata(
                registry,
                app_meta,
                context="openclaw install-root fallback",
                warn=False,
            )
            return registry.get_app("openclaw") or app_meta
    return None


def get_app_with_openclaw_fallback(
    registry: AppRegistry,
    name: str,
    *,
    infer_agent_id: Callable[[str], str],
    resolve_install_path: Callable[[str], Path],
    persist_registry_app_metadata: Callable[..., None],
) -> Optional[AppMetadata]:
    requested = str(name or "").strip()
    app_meta = registry.get_app(requested)
    if requested != "openclaw" or infer_agent_id(requested) != "openclaw":
        return app_meta

    playbook = resolve_playbook("openclaw")
    compose_subdir = str(getattr(playbook, "compose_subdir", ".") or ".").strip() or "."
    if app_meta is not None:
        install_dir = resolve_install_path(app_meta.install_path)
        if resolve_openclaw_runtime_dir(install_dir, compose_subdir) is not None:
            return app_meta

    fallback = fallback_openclaw_default_app(
        registry,
        requested,
        infer_agent_id=infer_agent_id,
        persist_registry_app_metadata=persist_registry_app_metadata,
    )
    return fallback or app_meta


def list_apps_with_openclaw_fallback(
    registry: AppRegistry,
    *,
    infer_agent_id: Callable[[str], str],
    resolve_install_path: Callable[[str], Path],
    persist_registry_app_metadata: Callable[..., None],
) -> List[AppMetadata]:
    apps = registry.list_apps()
    fallback = fallback_openclaw_default_app(
        registry,
        "openclaw",
        infer_agent_id=infer_agent_id,
        persist_registry_app_metadata=persist_registry_app_metadata,
    )
    if fallback is None:
        return apps

    playbook = resolve_playbook("openclaw")
    compose_subdir = str(getattr(playbook, "compose_subdir", ".") or ".").strip() or "."

    def _is_live_default_openclaw(app_meta: AppMetadata) -> bool:
        if str(app_meta.name or "").strip() != "openclaw":
            return False
        if infer_agent_id(str(app_meta.agent_id or app_meta.name or "")) != "openclaw":
            return False
        install_dir = resolve_install_path(app_meta.install_path)
        return resolve_openclaw_runtime_dir(install_dir, compose_subdir) is not None

    refreshed_apps = registry.list_apps()
    if any(_is_live_default_openclaw(app_meta) for app_meta in refreshed_apps):
        return refreshed_apps

    merged: List[AppMetadata] = []
    replaced_default = False
    for app_meta in refreshed_apps:
        if str(app_meta.name or "").strip() == "openclaw" and infer_agent_id(str(app_meta.agent_id or app_meta.name or "")) == "openclaw":
            if not replaced_default:
                merged.append(fallback)
                replaced_default = True
            continue
        merged.append(app_meta)
    if not replaced_default:
        merged.append(fallback)
    return merged


def can_reinstall_stale_default_openclaw_alias(
    registry: AppRegistry,
    agent: str,
    instance_name: str,
    *,
    infer_agent_id: Callable[[str], str],
    resolve_install_path: Callable[[str], Path],
    persist_registry_app_metadata: Callable[..., None],
) -> bool:
    alias = str(instance_name or "").strip()
    if alias != "openclaw" or infer_agent_id(agent) != "openclaw":
        return False
    app_meta = registry.get_app(alias)
    if app_meta is None:
        return False
    install_dir = resolve_install_path(app_meta.install_path)
    playbook = resolve_playbook("openclaw")
    compose_subdir = str(getattr(playbook, "compose_subdir", ".") or ".").strip() or "."
    if resolve_openclaw_runtime_dir(install_dir, compose_subdir) is not None:
        return False
    fallback = fallback_openclaw_default_app(
        registry,
        alias,
        infer_agent_id=infer_agent_id,
        persist_registry_app_metadata=persist_registry_app_metadata,
    )
    return fallback is None


def resolve_openclaw_install_dir(agent: str) -> Optional[Path]:
    registry = AppRegistry()
    app = registry.get_app(agent)
    if not app:
        return None
    install_dir = resolve_openclaw_registered_install_path(str(app.install_path or ""))
    if not install_dir.exists():
        return None
    playbook = resolve_playbook("openclaw")
    compose_subdir = str(getattr(playbook, "compose_subdir", ".") or ".").strip() or "."
    return resolve_openclaw_runtime_dir(install_dir, compose_subdir) or install_dir
