"""
transport.py - HTTP transport layer for Vela traces.

Posts trace and session data to the Vela ingest API.
Never blocks the main thread. Never raises exceptions. Fails silently.
"""

import atexit
import json
import time
import threading
import urllib.request
import urllib.error
import datetime

_api_key: str | None = None
_ingest_url: str = "https://vela-ai-eight.vercel.app/api/ingest"
_initialized: bool = False

_RETRY_COUNT = 3
_RETRY_DELAY = 1  # seconds

# Track pending threads so atexit can join them before process exits.
_pending_threads: list[threading.Thread] = []
_threads_lock = threading.Lock()


def _flush() -> None:
    """Wait up to 5 seconds for all pending threads to finish."""
    with _threads_lock:
        threads = list(_pending_threads)
    for t in threads:
        t.join(timeout=5)


def init(api_key: str, ingest_url: str | None = None) -> None:
    global _api_key, _ingest_url, _initialized
    _api_key = api_key
    if ingest_url:
        _ingest_url = ingest_url.rstrip("/") + "/api/ingest"
    _initialized = True
    atexit.register(_flush)


def _spawn(target, *args) -> None:
    """Start a non-daemon thread and track it for flushing."""
    t = threading.Thread(target=target, args=args, daemon=False)
    with _threads_lock:
        _pending_threads.append(t)
    t.start()


def _post(payload: dict) -> None:
    """POST to the ingest endpoint with retries. Fails silently."""
    if not _initialized:
        return
    data = json.dumps(payload).encode("utf-8")
    headers = {"Content-Type": "application/json"}

    for attempt in range(_RETRY_COUNT):
        try:
            req = urllib.request.Request(
                _ingest_url, data=data, headers=headers, method="POST"
            )
            with urllib.request.urlopen(req, timeout=10):
                pass
            return
        except urllib.error.HTTPError as e:
            body = e.read().decode("utf-8")
            print(f"[vela] ingest error {e.code}: {body!r}")
            return  # Don't retry on HTTP errors (4xx/5xx are not transient)
        except Exception as e:
            if attempt < _RETRY_COUNT - 1:
                time.sleep(_RETRY_DELAY)


def send_trace(trace_event: dict) -> None:
    """POST a trace event to the ingest API in a background thread."""
    if not _initialized:
        return
    _spawn(_post, {"type": "trace", "api_key": _api_key, "data": trace_event})


def create_session(
    session_id: str,
    label: str | None = None,
    cost_limit_usd: float | None = None,
    loop_limit: int | None = None,
) -> None:
    """Create a session row with status 'running'."""
    if not _initialized:
        return
    data: dict = {
        "id": session_id,
        "status": "running",
        "created_at": datetime.datetime.utcnow().isoformat() + "Z",
    }
    if label is not None:
        data["label"] = label
    if cost_limit_usd is not None:
        data["cost_limit_usd"] = cost_limit_usd
    if loop_limit is not None:
        data["loop_limit"] = loop_limit
    _spawn(_post, {"type": "session_create", "api_key": _api_key, "data": data})


def complete_session(session_id: str) -> None:
    """Update session status to 'complete'."""
    if not _initialized:
        return
    _spawn(_post, {
        "type": "session_update",
        "api_key": _api_key,
        "data": {"id": session_id, "status": "complete"},
    })


def error_session(session_id: str) -> None:
    """Update session status to 'error'."""
    if not _initialized:
        return
    _spawn(_post, {
        "type": "session_update",
        "api_key": _api_key,
        "data": {"id": session_id, "status": "error"},
    })


def halt_session(session_id: str, halted_reason: str) -> None:
    """Update session status to 'halted' with a reason."""
    if not _initialized:
        return
    _spawn(_post, {
        "type": "session_halt",
        "api_key": _api_key,
        "data": {
            "session_id": session_id,
            "halted_reason": halted_reason,
        },
    })
