"""
Brynq Volunteer Compute — opt-in idle benchmarking for the hive mind.

When the user's machine is idle, Brynq runs:
1. Model benchmarks (latency, throughput, quality)
2. Strategy A/B tests (which strategy works best for which prompt type)
3. Tool health checks (are marketplace tools still online?)
4. Agent quality scoring (real performance metrics)

Results are anonymized and sent to the cloud to improve:
- Model leaderboard (real-world rankings)
- Chain planner (strategy selection)
- Marketplace (tool availability, quality scores)

ALL data passes through the privacy filter. NO user content is sent.
Opt-in only. Default OFF. User must explicitly enable.
"""

import json
import logging
import os
import random
import threading
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

log = logging.getLogger("brynq.volunteer")

# Standardized benchmark prompts — no sensitive content
_BENCHMARK_PROMPTS = [
    {"type": "code", "prompt": "Write a Python function that checks if a string is a valid IPv4 address."},
    {"type": "analysis", "prompt": "What are the tradeoffs between SQL and NoSQL databases for a real-time analytics system?"},
    {"type": "creative", "prompt": "Write a one-paragraph product description for a smart water bottle that tracks hydration."},
    {"type": "reasoning", "prompt": "A train leaves City A at 60 mph. Another leaves City B (300 miles away) at 40 mph toward City A. When do they meet?"},
    {"type": "summarize", "prompt": "Summarize the key differences between microservices and monolithic architecture in 3 bullet points."},
]


class VolunteerCompute:
    """Manages opt-in volunteer compute for the Brynq network."""

    def __init__(self, data_dir: str | Path):
        self.data_dir = Path(data_dir)
        self._config_file = self.data_dir / "volunteer_config.json"
        self._results_dir = self.data_dir / "volunteer_results"
        self._results_dir.mkdir(parents=True, exist_ok=True)
        self._running = False
        self._thread: threading.Thread | None = None

    # ── Opt-in management ──────────────────────────────────────

    def is_enabled(self) -> bool:
        """Check if volunteer compute is opted in."""
        if not self._config_file.exists():
            return False
        try:
            config = json.loads(self._config_file.read_text(encoding="utf-8"))
            return config.get("enabled", False)
        except Exception:
            return False

    def opt_in(self) -> None:
        """Enable volunteer compute."""
        self._save_config({"enabled": True, "opted_in_at": datetime.now(timezone.utc).isoformat()})
        log.info("Volunteer compute enabled. Idle benchmarking will start automatically.")

    def opt_out(self) -> None:
        """Disable volunteer compute."""
        self._save_config({"enabled": False})
        self.stop()
        log.info("Volunteer compute disabled.")

    def _save_config(self, config: dict) -> None:
        self._config_file.parent.mkdir(parents=True, exist_ok=True)
        self._config_file.write_text(json.dumps(config, indent=2), encoding="utf-8")

    # ── Idle detection ─────────────────────────────────────────

    def _is_system_idle(self) -> bool:
        """Check if the system is idle enough to run benchmarks."""
        try:
            import psutil
            cpu = psutil.cpu_percent(interval=1)
            return cpu < 30  # Less than 30% CPU usage
        except ImportError:
            # Without psutil, assume idle if no user interaction for 5 min
            return True

    # ── Benchmark tasks ────────────────────────────────────────

    def _benchmark_model(self, model: str, prompt: dict) -> dict | None:
        """Run a single benchmark prompt against a model. Returns metrics."""
        try:
            import urllib.request
            body = json.dumps({
                "model": model,
                "messages": [{"role": "user", "content": prompt["prompt"]}],
                "stream": False,
            }).encode("utf-8")
            req = urllib.request.Request(
                "http://localhost:11434/api/chat",
                data=body,
                headers={"Content-Type": "application/json"},
            )
            start = time.time()
            with urllib.request.urlopen(req, timeout=60) as resp:
                result = json.loads(resp.read().decode("utf-8"))
            elapsed_ms = (time.time() - start) * 1000

            response_text = result.get("message", {}).get("content", "")
            return {
                "model": model,
                "prompt_type": prompt["type"],
                "latency_ms": round(elapsed_ms),
                "response_length": len(response_text),
                "token_estimate": len(response_text) // 4,
                "timestamp": datetime.now(timezone.utc).isoformat(),
            }
        except Exception as e:
            log.debug("Benchmark failed for %s: %s", model, e)
            return None

    def _get_local_models(self) -> list[str]:
        """Get list of locally available Ollama models."""
        try:
            import urllib.request
            req = urllib.request.Request("http://localhost:11434/api/tags")
            with urllib.request.urlopen(req, timeout=5) as resp:
                data = json.loads(resp.read().decode("utf-8"))
            return [m["name"] for m in data.get("models", [])]
        except Exception:
            return []

    def _check_tool_health(self, tool_id: str, endpoint: str) -> dict:
        """Ping a marketplace tool to check if it's online."""
        try:
            import urllib.request
            req = urllib.request.Request(endpoint, method="HEAD")
            start = time.time()
            with urllib.request.urlopen(req, timeout=10) as resp:
                latency_ms = (time.time() - start) * 1000
                return {
                    "tool_id": tool_id,
                    "status": "online",
                    "latency_ms": round(latency_ms),
                    "http_code": resp.status,
                    "timestamp": datetime.now(timezone.utc).isoformat(),
                }
        except Exception:
            return {
                "tool_id": tool_id,
                "status": "offline",
                "timestamp": datetime.now(timezone.utc).isoformat(),
            }

    # ── Main loop ──────────────────────────────────────────────

    def _run_cycle(self) -> dict:
        """Run one volunteer compute cycle. Returns results."""
        results = {"benchmarks": [], "tool_checks": [], "timestamp": datetime.now(timezone.utc).isoformat()}

        # 1. Model benchmarks
        models = self._get_local_models()
        if models:
            prompt = random.choice(_BENCHMARK_PROMPTS)
            model = random.choice(models)
            log.info("Benchmarking %s on %s task...", model, prompt["type"])
            benchmark = self._benchmark_model(model, prompt)
            if benchmark:
                results["benchmarks"].append(benchmark)

        # 2. Tool health checks (sample 5 random tools)
        try:
            from cloud.marketplace.catalog import MarketplaceCatalog
            for data_path in [Path(__file__).resolve().parent.parent.parent / "cloud" / "data", Path.cwd() / "cloud" / "data"]:
                if (data_path / "marketplace" / "catalog.json").exists():
                    cat = MarketplaceCatalog(data_path)
                    all_entries = cat.search("", limit=100)
                    api_entries = [e for e in all_entries if e.endpoint and e.install_type == "openapi_adapter"]
                    if api_entries:
                        sample = random.sample(api_entries, min(3, len(api_entries)))
                        for entry in sample:
                            log.info("Checking tool health: %s", entry.name)
                            check = self._check_tool_health(entry.id, entry.endpoint)
                            results["tool_checks"].append(check)
                    break
        except Exception:
            pass

        # Save results locally
        result_file = self._results_dir / f"cycle_{int(time.time())}.json"
        result_file.write_text(json.dumps(results, indent=2), encoding="utf-8")

        return results

    def _send_results_to_network(self, results: dict) -> bool:
        """Send anonymized results to the cloud for network intelligence."""
        try:
            from runtime.network.telemetry import TelemetryManager
            tm = TelemetryManager(self.data_dir)
            if not tm.is_opted_in():
                return False

            # Validate privacy before sending
            if not tm.validate_privacy(results):
                log.warning("Privacy validation failed — results not sent")
                return False

            import urllib.request
            body = json.dumps({
                "type": "volunteer_compute",
                "data": results,
            }).encode("utf-8")
            req = urllib.request.Request(
                "https://api.brynq.ai/v1/telemetry",
                data=body,
                headers={"Content-Type": "application/json"},
            )
            urllib.request.urlopen(req, timeout=10)
            return True
        except Exception:
            return False

    def _loop(self) -> None:
        """Background loop that runs benchmarks when idle."""
        while self._running:
            if not self.is_enabled():
                time.sleep(60)
                continue

            if not self._is_system_idle():
                time.sleep(120)  # Check again in 2 min
                continue

            try:
                results = self._run_cycle()
                benchmarks = len(results.get("benchmarks", []))
                checks = len(results.get("tool_checks", []))
                log.info("Volunteer cycle complete: %d benchmarks, %d tool checks", benchmarks, checks)

                # Try to send to network
                self._send_results_to_network(results)
            except Exception as e:
                log.debug("Volunteer cycle error: %s", e)

            # Wait 30 min before next cycle
            for _ in range(1800):
                if not self._running:
                    return
                time.sleep(1)

    # ── Start/Stop ─────────────────────────────────────────────

    def start(self) -> None:
        """Start the volunteer compute background thread."""
        if self._running:
            return
        if not self.is_enabled():
            log.info("Volunteer compute not enabled. Use /volunteer on to enable.")
            return
        self._running = True
        self._thread = threading.Thread(target=self._loop, daemon=True, name="brynq-volunteer")
        self._thread.start()
        log.info("Volunteer compute started. Running benchmarks when idle.")

    def stop(self) -> None:
        """Stop the volunteer compute background thread."""
        self._running = False
        if self._thread:
            self._thread.join(timeout=5)
        log.info("Volunteer compute stopped.")

    # ── Stats ──────────────────────────────────────────────────

    def get_stats(self) -> dict:
        """Get volunteer compute statistics."""
        result_files = list(self._results_dir.glob("cycle_*.json"))
        total_benchmarks = 0
        total_checks = 0
        models_tested = set()

        for f in result_files:
            try:
                data = json.loads(f.read_text(encoding="utf-8"))
                for b in data.get("benchmarks", []):
                    total_benchmarks += 1
                    models_tested.add(b.get("model", ""))
                total_checks += len(data.get("tool_checks", []))
            except Exception:
                continue

        return {
            "enabled": self.is_enabled(),
            "total_cycles": len(result_files),
            "total_benchmarks": total_benchmarks,
            "total_tool_checks": total_checks,
            "models_tested": list(models_tested),
        }
