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

import questionary

from armorer.agent_sdk import tool
from armorer.catalog import resolve_playbook
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, app_name: str) -> Optional[AppMetadata]:
    requested = str(app_name or "").strip()
    if 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 None:
            continue
        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,
        )
        try:
            registry.add_app(app_meta)
        except OSError:
            pass
        return registry.get_app("openclaw") or app_meta
    return None


def _get_app_with_openclaw_fallback(registry: AppRegistry, app_name: str) -> Optional[AppMetadata]:
    requested = str(app_name or "").strip()
    app = registry.get_app(requested)
    if requested != "openclaw":
        return app

    playbook = resolve_playbook("openclaw")
    compose_subdir = str(getattr(playbook, "compose_subdir", ".") or ".").strip() or "."
    if app is not None and _resolve_openclaw_runtime_dir(Path(app.install_path), compose_subdir) is not None:
        return app

    fallback = _fallback_openclaw_default_app(registry, requested)
    return fallback or app

@tool(category="interaction", verification_level="user_input")
def ask_selection(message: str, options: List[str]) -> str:
    """
    Present a selection menu to the user and return the chosen option.
    
    Args:
        message: The question to ask the user.
        options: A list of string options for the user to choose from.
        
    Returns:
        The string value of the selected option.
    """
    if not options:
        return "No options provided."
        
    choice = questionary.select(
        message,
        choices=options
    ).ask()
    
    return choice or ""

@tool(category="interaction", verification_level="registry_write")
def update_app_metadata(app_name: str, key: str, value: Any) -> str:
    """
    Update a specific metadata field for a registered application.
    Useful when discovering new monitor targets or config files.
    
    Args:
        app_name: The name of the application to update.
        key: The metadata field to update (e.g., 'monitor_targets').
        value: The new value for the field (for monitor_targets, this should be a list of strings).
        
    Returns:
        A success or error message.
    """
    registry = AppRegistry()
    app = _get_app_with_openclaw_fallback(registry, app_name)
    if not app:
        return f"Error: Application '{app_name}' not found in registry."
    
    if not hasattr(app, key):
        return f"Error: Field '{key}' is not a valid metadata field."

    # If it's a list (like monitor_targets), we append unique values
    if isinstance(getattr(app, key), list) and isinstance(value, list):
        current = getattr(app, key)
        for v in value:
            if v not in current:
                current.append(v)
        setattr(app, key, current)
    else:
        setattr(app, key, value)
        
    registry.add_app(app)
    return f"Successfully updated '{key}' for {app_name}."

@tool(category="interaction", verification_level="redaction")
def redact_secret(app_name: str, secret: str, target_files: Optional[List[str]] = None) -> str:
    """
    Hunt down and redact a specific secret from the specified files (or all files if none provided).
    
    Args:
        app_name: The name of the application to sanitize.
        secret: The exact string (API key, password) to redact.
        target_files: Optional list of specific file paths or glob patterns to sanitize.
    """
    import subprocess
    
    registry = AppRegistry()
    app = _get_app_with_openclaw_fallback(registry, app_name)
    if not app:
        return f"Error: Application '{app_name}' not found."
    
    install_path = Path(app.install_path)
    
    try:
        files_to_redact = []
        
        # 1. Expand targets (handling globs)
        if target_files:
            for pattern in target_files:
                # If absolute, use as is, else relative to install_path
                p = Path(pattern)
                search_root = install_path if not p.is_absolute() else Path("/")
                search_pattern = str(p.relative_to(search_root)) if p.is_absolute() and str(p).startswith(str(search_root)) else pattern
                
                found = list(search_root.glob(search_pattern))
                files_to_redact.extend([str(f) for f in found if f.is_file()])
        else:
            # Recursive search for all occurrences
            result = subprocess.run(
                ["grep", "-rl", secret, str(install_path)],
                capture_output=True, text=True, check=False
            )
            files_to_redact = result.stdout.strip().splitlines()
        
        if not files_to_redact:
            return f"No valid files found to redact in {app_name}."
        
        # 2. Perform redaction (Platform Agnostic)
        redacted_count = 0
        errors = []
        
        for file_path in set(files_to_redact):
            try:
                # Read content
                p = Path(file_path)
                content = p.read_text(encoding="utf-8", errors="ignore")
                
                if secret not in content:
                    continue
                    
                # Replace secret
                new_content = content.replace(secret, "[REDACTED]")
                
                # Write back (try standard write first)
                try:
                    p.write_text(new_content, encoding="utf-8")
                    redacted_count += 1
                except PermissionError:
                    # Retry with permission fix
                    subprocess.run(["chmod", "u+w", file_path], check=True, capture_output=True)
                    p.write_text(new_content, encoding="utf-8")
                    redacted_count += 1
                    
            except Exception as e:
                errors.append(f"{Path(file_path).name}: {str(e)}")
            
        msg = f"🛡️  Redaction Complete: Successfully masked {redacted_count} files in {app_name}."
        if errors:
            msg += f"\n[dim]Note: Failed on {len(errors)} files ({', '.join(errors)}).[/dim]"
        return msg
        
    except Exception as e:
        return f"Redaction logic failed: {e}"
