# Path: scaffold/artisans/env_tools.py
# ------------------------------------

import json
import os
import shutil
import time
import tempfile
import platform
import subprocess
from pathlib import Path
from typing import Dict, Any, List, Tuple
from concurrent.futures import ThreadPoolExecutor

import requests
from rich.table import Table
from rich.panel import Panel
from rich.prompt import Confirm
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, FileSizeColumn

from ..core.artisan import BaseArtisan
from ..interfaces.base import ScaffoldResult, Artifact
from ..interfaces.requests import ContextFreezeRequest, BenchRequest, PurgeRequest
from ..help_registry import register_artisan
from ..utils import atomic_write, get_human_readable_size
from ..core.cortex.knowledge import KnowledgeBase
from ..core.system.manager import System

# --- THE DIVINE SUMMONS ---
try:
    import psutil

    PSUTIL_AVAILABLE = True
except ImportError:
    PSUTIL_AVAILABLE = False


@register_artisan("freeze-context")
class ContextFreezeArtisan(BaseArtisan[ContextFreezeRequest]):
    """
    =============================================================================
    == THE CONTEXT FREEZER (V-Ω-DEBUG-SNAPSHOT)                                ==
    =============================================================================
    LIF: 10,000,000,000

    Captures the state of the machine for reproduction of bugs.
    It automatically sanitizes secrets before inscription.
    """

    def execute(self, request: ContextFreezeRequest) -> ScaffoldResult:
        self.logger.info("The Context Freezer awakens. Capturing reality state...")

        # 1. Harvest Gnosis
        sys_info = System.get_capabilities()
        platform_info = {
            "os": platform.system(),
            "release": platform.release(),
            "version": platform.version(),
            "arch": platform.machine(),
            "python": platform.python_version(),
            "scaffold_version": "0.1.0"  # Prophecy: Import from __init__
        }

        # 2. Tool Versioning (The Roll Call)
        tools = {}
        for tool in ["git", "docker", "node", "npm", "poetry", "go", "rustc", "cargo"]:
            path = shutil.which(tool)
            if path:
                try:
                    # Most tools accept --version
                    res = subprocess.run([path, "--version"], capture_output=True, text=True, timeout=1)
                    tools[tool] = res.stdout.strip()
                except:
                    tools[tool] = "Present (Version Unknown)"
            else:
                tools[tool] = "Missing"

        # 3. Environment (Sanitized)
        env_vars = {}
        if request.include_env:
            for k, v in os.environ.items():
                if any(secret in k.lower() for secret in ['key', 'token', 'pass', 'secret', 'auth']):
                    env_vars[k] = "****** [REDACTED] ******"
                else:
                    env_vars[k] = v

        # 4. Scaffold Internal State
        # We access the engine's current variables, scrubbing secrets
        scaffold_vars = {}
        # (Assuming we can access engine.context or similar in V7 architecture)
        # For now, we leave this as a placeholder or grab from request if available.

        snapshot = {
            "timestamp": time.time(),
            "platform": platform_info,
            "system_capabilities": sys_info,
            "tools": tools,
            "environment": env_vars,
            "notes": "Generated by `scaffold freeze-context`"
        }

        # 5. Inscription
        output_path = self.project_root / (request.output_file or "debug_context.json")
        atomic_write(output_path, json.dumps(snapshot, indent=2), self.logger, self.project_root)

        return self.success(
            f"Context frozen to [cyan]{output_path.name}[/cyan].",
            artifacts=[Artifact(path=output_path, type="file", action="created")]
        )


@register_artisan("bench")
class BenchArtisan(BaseArtisan[BenchRequest]):
    """
    =============================================================================
    == THE SYSTEM BENCHMARKER (V-Ω-HARDWARE-TRUTH)                             ==
    =============================================================================
    LIF: 10,000,000,000

    Conducts the Trials of Vitality to ensure the host machine is worthy.
    """

    def execute(self, request: BenchRequest) -> ScaffoldResult:
        if not PSUTIL_AVAILABLE:
            return self.failure("The Benchmarker requires 'psutil'. pip install psutil")

        results = {}
        self.console.rule("[bold magenta]The Trials of Vitality[/bold magenta]")

        # --- TRIAL I: THE CPU (FLOAT MATH) ---
        if "cpu" in request.suites:
            with self.console.status("[cyan]Conducting CPU Stress Test...[/cyan]"):
                start = time.time()
                # Heavy floating point math: 10 million sqrts
                _ = [x ** 0.5 for x in range(10_000_000)]
                duration = time.time() - start
                score = 10.0 / duration  # Arbitrary score
                rating = "Excellent" if duration < 1.5 else "Good" if duration < 3.0 else "Sluggish"
                results["CPU"] = f"{duration:.3f}s ({rating})"

        # --- TRIAL II: THE DISK (IOPS/THROUGHPUT) ---
        if "disk" in request.suites:
            with self.console.status("[yellow]Conducting I/O Write Test (512MB)...[/yellow]"):
                try:
                    with tempfile.NamedTemporaryFile(delete=True) as tf:
                        # Write 512MB
                        chunk = b"0" * 1024 * 1024  # 1MB
                        start = time.time()
                        for _ in range(512):
                            tf.write(chunk)
                        tf.flush()
                        os.fsync(tf.file.fileno())  # Force to disk
                        duration = time.time() - start

                        mb_sec = 512 / duration
                        rating = "Blazing (NVMe)" if mb_sec > 1000 else "Fast (SSD)" if mb_sec > 300 else "Slow (HDD)"
                        results["Disk Write"] = f"{mb_sec:.0f} MB/s ({rating})"
                except Exception as e:
                    results["Disk Write"] = f"Failed: {e}"

        # --- TRIAL III: THE NETWORK (LATENCY) ---
        if "net" in request.suites:
            with self.console.status("[blue]Ping Celestial Relays...[/blue]"):
                targets = ["https://1.1.1.1", "https://google.com", "https://github.com"]
                latencies = []
                for t in targets:
                    try:
                        start = time.time()
                        requests.get(t, timeout=2)
                        lat = (time.time() - start) * 1000
                        latencies.append(lat)
                    except:
                        pass

                if latencies:
                    avg_lat = sum(latencies) / len(latencies)
                    rating = "Fiber-optic" if avg_lat < 20 else "Broadband" if avg_lat < 100 else "Laggy"
                    results["Net Latency"] = f"{avg_lat:.0f}ms ({rating})"
                else:
                    results["Net Latency"] = "Offline / Blocked"

        # Display Results
        table = Table(title="System Vitality Report")
        table.add_column("Trial", style="bold white")
        table.add_column("Result", style="cyan")

        for k, v in results.items():
            table.add_row(k, v)

        self.console.print(table)
        return self.success("The Trials are complete.")


@register_artisan("purge")
class PurgeArtisan(BaseArtisan[PurgeRequest]):
    """
    =============================================================================
    == THE CLEANER (V-Ω-ABYSSAL-VOID-MAKER)                                    ==
    =============================================================================
    LIF: 100,000,000,000

    Recursively hunts down generated artifacts (node_modules, venv, etc)
    and offers to return them to the void.
    """

    def execute(self, request: PurgeRequest) -> ScaffoldResult:
        root = (self.project_root / request.target).resolve()

        self.logger.info(f"The Cleaner scans [cyan]{root}[/cyan] for artifacts...")

        # The Target List from the Knowledge Base
        # We explicitly target directories known to be regenerate-able
        targets = KnowledgeBase.ABYSS_DIRECTORIES
        # Filter out safe ones like .git
        targets = {t for t in targets if t not in ['.git', '.scaffold']}

        found_artifacts = []
        total_size = 0

        # 1. The Gaze of Discovery
        with Progress(
                SpinnerColumn(), TextColumn("[progress.description]{task.description}"),
                transient=True, console=self.console
        ) as progress:
            task = progress.add_task("Scanning filesystem...", total=None)

            for dirpath, dirnames, filenames in os.walk(root):
                # Don't descend into hidden dirs we found (e.g. don't scan inside node_modules)
                # Modify dirnames in-place to prune search

                to_remove = []
                for d in dirnames:
                    if d in targets:
                        full_path = Path(dirpath) / d
                        size = self._calculate_dir_size(full_path)

                        # Apply Threshold
                        if size / (1024 * 1024) >= request.threshold_mb:
                            found_artifacts.append((full_path, size))
                            total_size += size

                        # Prune from walk
                        to_remove.append(d)

                for d in to_remove:
                    dirnames.remove(d)

        # 2. The Proclamation
        if not found_artifacts:
            return self.success("The sanctum is clean. No artifacts found.")

        table = Table(title=f"Artifacts Found ({get_human_readable_size(total_size)})", border_style="red")
        table.add_column("Sanctum", style="cyan")
        table.add_column("Artifact", style="bold yellow")
        table.add_column("Size", justify="right")

        for path, size in sorted(found_artifacts, key=lambda x: x[1], reverse=True):
            rel_path = path.relative_to(root)
            table.add_row(str(rel_path.parent), rel_path.name, get_human_readable_size(size))

        self.console.print(table)

        # 3. The Rite of Annihilation
        if request.dry_run:
            return self.success("Dry run complete. No souls were annihilated.")

        if not request.force:
            if not Confirm.ask(f"[bold red]Annihilate these {len(found_artifacts)} artifacts?[/bold red]"):
                return self.success("The Cleaner stays its hand.")

        # 4. Execution
        annihilated_count = 0
        reclaimed_bytes = 0

        with self.console.status("[bold red]Purging...[/bold red]"):
            for path, size in found_artifacts:
                try:
                    shutil.rmtree(path)
                    annihilated_count += 1
                    reclaimed_bytes += size
                except Exception as e:
                    self.logger.warn(f"Failed to purge {path.name}: {e}")

        return self.success(
            f"Purge complete. Reclaimed {get_human_readable_size(reclaimed_bytes)}.",
            data={"reclaimed_bytes": reclaimed_bytes, "count": annihilated_count}
        )

    def _calculate_dir_size(self, path: Path) -> int:
        """Calculates size recursively. Handles errors gracefully."""
        total = 0
        try:
            for p in path.rglob('*'):
                if p.is_file():
                    total += p.stat().st_size
        except Exception:
            pass
        return total