"""
Standard-library integration.

Captures:
* stdout / stderr writes
* Uncaught exceptions (``sys.excepthook``)
* Thread exceptions (``threading.excepthook``, Python 3.8+)
* Process signals (SIGTERM, SIGINT)
"""

import sys
import signal
import threading
import traceback
import time

from overcast_sdk.integrations import Integration
from overcast_sdk.utils import capture_internal, is_sending, _originals


class _StreamCapture:
    """Wraps ``sys.stdout`` or ``sys.stderr`` to forward writes to Overcast."""

    def __init__(self, original, level):
        self._original = original
        self._level = level

    def write(self, text):
        self._original.write(text)
        if is_sending() or not text or not text.strip():
            return
        if text.strip().startswith("[Overcast]"):
            return
        with capture_internal:
            from overcast_sdk.client import get_client
            client = get_client()
            if client:
                stream = "stdout" if self._level == "INFO" else "stderr"
                client.enqueue(self._level, text.strip(), {"type": stream})

    def flush(self):
        self._original.flush()

    def __getattr__(self, name):
        return getattr(self._original, name)


class StdlibIntegration(Integration):
    identifier = "stdlib"

    @staticmethod
    def setup_once():
        from overcast_sdk.client import get_client

        _install_stdout()
        _install_stderr()
        _install_excepthook()
        _install_threading_excepthook()
        _install_signal_handlers()


# ── Installers ────────────────────────────────────────────────────────────────

def _install_stdout():
    _originals["stdout"] = sys.stdout
    sys.stdout = _StreamCapture(sys.stdout, "INFO")


def _install_stderr():
    _originals["stderr"] = sys.stderr
    sys.stderr = _StreamCapture(sys.stderr, "ERROR")


def _install_excepthook():
    _originals["excepthook"] = sys.excepthook

    def _hook(exc_type, exc_value, exc_tb):
        with capture_internal:
            from overcast_sdk.client import get_client
            client = get_client()
            if client:
                tb = "".join(traceback.format_exception(exc_type, exc_value, exc_tb))
                client.enqueue("ERROR", f"Uncaught {exc_type.__name__}: {exc_value}", {
                    "type": "uncaught_exception",
                    "error": {
                        "type": exc_type.__name__,
                        "message": str(exc_value),
                        "traceback": tb,
                    },
                })
                client.flush(timeout=2.0)

        orig = _originals.get("excepthook")
        if orig:
            orig(exc_type, exc_value, exc_tb)

    sys.excepthook = _hook


def _install_threading_excepthook():
    if sys.version_info < (3, 8):
        return

    _originals["threading_excepthook"] = threading.excepthook

    def _hook(args):
        with capture_internal:
            from overcast_sdk.client import get_client
            client = get_client()
            if client:
                tb = "".join(
                    traceback.format_exception(
                        args.exc_type, args.exc_value, args.exc_traceback,
                    )
                )
                thread_name = args.thread.name if args.thread else "unknown"
                client.enqueue("ERROR", f"Thread exception in {thread_name}: {args.exc_value}", {
                    "type": "thread_exception",
                    "thread_name": thread_name,
                    "error": {
                        "type": args.exc_type.__name__,
                        "message": str(args.exc_value),
                        "traceback": tb,
                    },
                })

        orig = _originals.get("threading_excepthook")
        if orig:
            orig(args)

    threading.excepthook = _hook


def _install_signal_handlers():
    for sig_name in ("SIGTERM", "SIGINT"):
        sig = getattr(signal, sig_name, None)
        if sig is None:
            continue
        prev = signal.getsignal(sig)

        def _handler(signum, frame, _prev=prev, _name=sig_name):
            with capture_internal:
                from overcast_sdk.client import get_client
                client = get_client()
                if client:
                    client.enqueue("INFO", f"Process received {_name}", {
                        "type": "process_signal",
                        "signal": _name,
                    })
                    client.flush(timeout=2.0)
            if callable(_prev) and _prev not in (signal.SIG_DFL, signal.SIG_IGN):
                _prev(signum, frame)

        try:
            signal.signal(sig, _handler)
        except (OSError, ValueError):
            pass  # not main thread, or signal not supported
