from typing import Dict, Any, List
from pathlib import Path

from armorer.agent_sdk import tool
import subprocess
import json

from armorer import docker_runner

@tool(category="verification", verification_level="deployment_verification", safe_for_retrieval=True)
def verify_deployment(install_path: str) -> str:
    """
    Verify the health and configuration of the installed application.
    Performs pre-flight config validation and runtime container checks.
    
    Args:
        install_path: The absolute path where the application is installed.
    """
    import logging
    import re
    logger = logging.getLogger("verify_deployment")
    
    try:
        report = []

        # 1. Check Docker Compose version
        version_cmd = docker_runner.compose_version(short=True)
        
        if version_cmd.returncode == 0:
            version_str = version_cmd.stdout.strip()
            clean_version = version_str.lstrip('v')
            try:
                version_parts = [int(p) for p in clean_version.split('.')[:3]]
                current_version = tuple(version_parts)
                required_version = (2, 24, 0)
                
                if current_version < required_version:
                    report.append(f"WARNING: Docker Compose version {version_str} is below {'.'.join(map(str, required_version))}. "
                                "Modern features like 'env_file' maps or 'develop' blocks will cause failures.")
            except: pass

        # 2. Pre-flight: Config Validation
        config_check = docker_runner.compose_config(cwd=Path(install_path), quiet=True)
        if config_check.returncode != 0:
            err = config_check.stderr
            msg = f"CRITICAL: 'docker compose config' failed.\nError details: {err}"
            if "env_file" in err and "expected type 'string'" in err:
                msg += "\n\n💡 REASON: You are using modern 'env_file' map syntax on an old Docker version. " \
                       "Fix it by converting list items to simple strings (e.g. '- .env')."
            return msg

        # 3. Runtime: Container Status
        result = docker_runner.compose_ps(cwd=Path(install_path), args=["--format", "json"])
        
        if result.returncode != 0:
            return f"Error checking runtime status: {result.stderr}"

        containers = []
        raw_output = result.stdout or ""
        
        # If output is empty, it might mean no containers, which is fine if return code was 0.
        if not raw_output.strip():
             return "CONFIG VALID: Configuration is valid, but NO containers are running. Action required: Run 'docker compose up -d' to start services."

        try:
             # Docker compose ps --format json outputs a JSON objects (one per line) or a JSON array
             # We need to handle both.
             
             # Attempt 1: Try parsing the whole output as a JSON array
             try:
                 data = json.loads(raw_output)
                 if isinstance(data, list): 
                     containers = data
                 else:
                     containers = [data]
             except json.JSONDecodeError:
                 # Attempt 2: Try parsing line by line (NDJSON style which docker sometimes uses)
                 for line in raw_output.splitlines():
                     if line.strip():
                         containers.append(json.loads(line))
                         
        except json.JSONDecodeError as e:
             return f"Error parsing 'docker compose ps' output. It returned invalid JSON:\n{raw_output}\n\nParser error: {e}"
        
        if not containers:
            return "CHECK PASSED: Configuration is valid, but no containers are running yet. Proceed to 'docker compose up -d'."

        summary = []
        crashed = []
        for c in containers:
            state = c.get("State", "unknown")
            status = c.get("Status", "unknown")
            name = c.get("Name", "unknown")
            exit_code = c.get("ExitCode", 0)
            
            if exit_code != 0:
                crashed.append(name)
                state = f"CRASHED (Exit {exit_code})"
                
            summary.append(f"- {name}: {state} ({status})")
            
        report.append("Deployment Status:\n" + "\n".join(summary))
        
        if crashed:
            report.append("\nCRITICAL ISSUES DETECTED:\nThe following containers have crashed:\n" + "\n".join(crashed))
            report.append("\nFetching logs for crashed containers...\n")
            
            for c_name in crashed:
                logs = docker_runner.docker_logs(c_name, tail=50)
                report.append(f"\n--- Logs for {c_name} ---\n{logs.stdout}\n{logs.stderr}\n")
        
        return "\n".join(report)

    except Exception as e:
        return f"Verification tool failed: {e}"

@tool(category="verification", verification_level="static_analysis", safe_for_retrieval=True)
def analyze_dockerfile(path: str) -> str:
    """
    Perform a static security and best-practice analysis of a Dockerfile.
    
    Args:
        path: Path to the Dockerfile.
    """
    from pathlib import Path
    import re
    
    try:
        p = Path(path)
        if not p.exists():
            return f"Error: {path} not found."
            
        content = p.read_text()
        issues = []
        
        # 1. Check for USER directive (non-root)
        if not re.search(r"^USER\s+", content, re.MULTILINE):
            issues.append("- SECURITY: No 'USER' directive found. Container likely runs as root.")
            
        # 2. Check for multi-stage build
        if content.count("FROM ") < 2:
            issues.append("- BEST PRACTICE: Not using multi-stage builds. Image might be larger than necessary.")
            
        # 3. Check for package manager cleanup
        if "apt-get install" in content and "rm -rf /var/lib/apt/lists/*" not in content:
            issues.append("- BEST PRACTICE: Missing cleanup of apt cache (rm -rf /var/lib/apt/lists/*).")
            
        # 4. Check for secret leaks (heuristic)
        secrets = ["API_KEY", "SECRET", "PASSWORD", "TOKEN"]
        for s in secrets:
            if re.search(fr"ENV\s+{s}=", content, re.IGNORECASE):
                issues.append(f"- SECURITY: Found sensitive variable '{s}' set via ENV. Use build-args or secret mounts instead.")

        if not issues:
            return "✅ Dockerfile looks good (basic checks passed)."
            
        return "Dockerfile Analysis Results:\n" + "\n".join(issues)
        
    except Exception as e:
        return f"Analysis tool failed: {e}"
