# Telemetry Module for Jupyters
# Buffers events and sends them in batches to the analytics endpoint.
# Fire-and-forget: failures never block MCP tool execution.

import threading
import time
import json
import urllib.request
import urllib.error
import logging
import atexit
from typing import List, Dict, Any, Optional
from functools import wraps

logger = logging.getLogger("jupyters.telemetry")

TELEMETRY_URL = "https://www.jupyters.fun/api/telemetry"
FLUSH_INTERVAL = 30  # seconds
MAX_BATCH_SIZE = 50


class TelemetryManager:
    """Buffered telemetry client. Singleton."""

    _instance: Optional['TelemetryManager'] = None

    def __init__(self):
        self._buffer: List[Dict[str, Any]] = []
        self._lock = threading.Lock()
        self._timer: Optional[threading.Timer] = None
        self._machine_id: Optional[str] = None
        self._started = False

    @classmethod
    def instance(cls) -> 'TelemetryManager':
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance

    def start(self):
        """Start the flush timer and register shutdown handler."""
        if self._started:
            return
        self._started = True
        self._schedule_flush()
        atexit.register(self.shutdown)
        self.track("session_start", metadata=self._session_metadata())

    def _session_metadata(self) -> dict:
        import sys
        try:
            from importlib.metadata import version
            server_version = version("jupyters-server")
        except Exception:
            server_version = "unknown"
        return {
            "python_version": sys.version.split()[0],
            "server_version": server_version,
        }

    def _get_machine_id(self) -> str:
        if self._machine_id is None:
            try:
                from context_engine.license import LicenseManager
                self._machine_id = LicenseManager.instance()._get_machine_id()
            except Exception:
                import hashlib, platform, getpass
                data = f"{platform.node()}-{getpass.getuser()}-{platform.system()}"
                self._machine_id = hashlib.sha256(data.encode()).hexdigest()[:16]
        return self._machine_id

    def track(self, event_type: str, event_name: str = None, metadata: dict = None):
        """Buffer an event. Thread-safe."""
        with self._lock:
            self._buffer.append({
                "type": event_type,
                "name": event_name,
                "metadata": metadata or {},
            })
            if len(self._buffer) >= MAX_BATCH_SIZE:
                self._flush_async()

    def _schedule_flush(self):
        self._timer = threading.Timer(FLUSH_INTERVAL, self._periodic_flush)
        self._timer.daemon = True
        self._timer.start()

    def _periodic_flush(self):
        self._flush()
        if self._started:
            self._schedule_flush()

    def _flush_async(self):
        threading.Thread(target=self._flush, daemon=True).start()

    def _flush(self):
        with self._lock:
            if not self._buffer:
                return
            events = self._buffer[:]
            self._buffer.clear()

        try:
            payload = json.dumps({
                "machine_id": self._get_machine_id(),
                "events": events,
            }).encode("utf-8")

            req = urllib.request.Request(
                TELEMETRY_URL,
                data=payload,
                headers={"Content-Type": "application/json"},
                method="POST",
            )
            urllib.request.urlopen(req, timeout=5)
        except Exception as e:
            logger.debug(f"Telemetry flush failed (non-critical): {e}")

    def shutdown(self):
        """Final synchronous flush on shutdown."""
        self._started = False
        if self._timer:
            self._timer.cancel()
        self.track("session_end")
        self._flush()


def tracked_tool(func):
    """Decorator to track MCP tool invocations."""
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        try:
            result = func(*args, **kwargs)
            duration = int((time.time() - start) * 1000)
            TelemetryManager.instance().track(
                "tool_call", func.__name__,
                {"duration_ms": duration, "success": True}
            )
            return result
        except Exception as e:
            duration = int((time.time() - start) * 1000)
            TelemetryManager.instance().track(
                "error", func.__name__,
                {"duration_ms": duration, "error_type": type(e).__name__}
            )
            raise
    return wrapper


def track_feature_gate(feature: str):
    """Track when a free user hits a Pro feature wall."""
    TelemetryManager.instance().track("feature_gate", feature)
