"""
Auto-capture logging for Outpost9 agents.

When running inside Outpost9, stdout is automatically captured and sent
to the Outpost9 log collector. No user code changes required.

Supports per-request run ID via context variable for isolated log streams.
"""

import sys
import os
import threading
import time
import json
import contextvars
from typing import TextIO, List, Optional
import urllib.request
import urllib.error


# Context variable for per-request run ID
_current_run_id: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar(
    'current_run_id', default=None
)

# Internal token for secure log submission (set by Outpost9 when creating sandbox)
_INTERNAL_TOKEN = os.environ.get("OUTPOST9_INTERNAL_TOKEN")


class LogCapture:
    """
    Captures stdout and sends logs to Outpost9 log collector.

    This class wraps sys.stdout and:
    1. Passes through all writes to the original stdout
    2. Buffers log lines with their run ID (captured at write time)
    3. Sends batched logs to the log collector endpoint
    """

    def __init__(
        self,
        agent_id: str,
        log_url: str,
        original_stdout: TextIO,
        batch_interval: float = 0.1,
        max_batch_size: int = 100,
        run_id: Optional[str] = None,
    ):
        self.agent_id = agent_id
        self.run_id = run_id
        self.log_url = log_url
        self.original = original_stdout
        # Buffer stores tuples of (message, run_id) to capture run_id at write time
        self.buffer: List[tuple] = []
        self.buffer_lock = threading.Lock()
        self.batch_interval = batch_interval
        self.max_batch_size = max_batch_size
        self._running = True
        self._sender_thread: Optional[threading.Thread] = None

        # Start background sender thread
        self._start_sender()

    def write(self, text: str) -> int:
        """Write text to stdout and capture for logging."""
        # Always pass through to original stdout
        result = self.original.write(text)

        # Capture non-empty text for logging
        text_stripped = text.strip()
        if text_stripped:
            # Capture run_id NOW (at write time) since contextvars don't propagate to threads
            current_run_id = _current_run_id.get() or self.run_id
            with self.buffer_lock:
                self.buffer.append((text_stripped, current_run_id))

                # Flush immediately if batch is large
                if len(self.buffer) >= self.max_batch_size:
                    self._flush_buffer()

        return result

    def flush(self) -> None:
        """Flush stdout."""
        self.original.flush()

    def fileno(self) -> int:
        """Return file descriptor."""
        return self.original.fileno()

    def isatty(self) -> bool:
        """Check if output is a TTY."""
        return self.original.isatty()

    def _start_sender(self) -> None:
        """Start background thread to send logs."""
        def sender():
            while self._running:
                time.sleep(self.batch_interval)
                self._flush_buffer()

        self._sender_thread = threading.Thread(target=sender, daemon=True)
        self._sender_thread.start()

    def _flush_buffer(self) -> None:
        """Send buffered logs to collector."""
        with self.buffer_lock:
            if not self.buffer:
                return

            logs = self.buffer[:]
            self.buffer.clear()

        try:
            # Group logs by run_id and send each group separately
            logs_by_run: dict = {}
            for msg, run_id in logs:
                if run_id not in logs_by_run:
                    logs_by_run[run_id] = []
                logs_by_run[run_id].append(msg)

            for run_id, messages in logs_by_run.items():
                self._send_logs(messages, run_id)
        except Exception as e:
            # Log send failed - write to original stderr for debugging
            sys.stderr.write(f"[outpost9] Failed to send logs: {e}\n")

    def _send_logs(self, logs: List[str], run_id: Optional[str] = None) -> None:
        """Send logs to the collector endpoint."""
        # Skip if no run_id - don't send to "default" stream
        if not run_id:
            return

        payload = {
            "agentId": self.agent_id,
            "runId": run_id,
            "logs": [{"type": "app", "msg": msg} for msg in logs],
        }

        data = json.dumps(payload).encode("utf-8")

        headers = {
            "Content-Type": "application/json",
            "User-Agent": "Outpost9-SDK/0.0.1",
        }
        if _INTERNAL_TOKEN:
            headers["X-Internal-Token"] = _INTERNAL_TOKEN

        req = urllib.request.Request(
            self.log_url,
            data=data,
            headers=headers,
            method="POST",
        )

        try:
            with urllib.request.urlopen(req, timeout=5) as response:
                response.read()  # Consume response
        except urllib.error.URLError:
            pass  # Silently ignore network errors

    def stop(self) -> None:
        """Stop the log capture."""
        self._running = False
        if self._sender_thread:
            self._sender_thread.join(timeout=1)

        # Flush any remaining logs
        self._flush_buffer()


# Global capture instance
_capture: Optional[LogCapture] = None


def _setup_auto_logging(agent_id: str, log_url: str, run_id: Optional[str] = None) -> None:
    """
    Set up automatic stdout capture.

    Called automatically when the SDK detects it's running inside Outpost9.
    """
    global _capture

    if _capture is not None:
        return  # Already set up

    _capture = LogCapture(agent_id, log_url, sys.stdout, run_id=run_id)
    sys.stdout = _capture  # type: ignore

    # Log that we're starting
    run_info = f" (run: {run_id})" if run_id else ""
    print(f"[outpost9] Auto-logging enabled for agent {agent_id}{run_info}")


def _teardown_auto_logging() -> None:
    """Tear down auto-logging (for testing)."""
    global _capture

    if _capture is not None:
        sys.stdout = _capture.original
        _capture.stop()
        _capture = None


def set_run_id(run_id: Optional[str]) -> None:
    """
    Set the current run ID for this request context.

    This is called automatically when the gateway passes X-Run-Id header.
    All logs from this request will be tagged with this run ID.
    """
    _current_run_id.set(run_id)


def get_run_id() -> Optional[str]:
    """Get the current run ID for this request context."""
    return _current_run_id.get()
