"""PulseRadar Python SDK — send logs to PulseRadar."""

from __future__ import annotations

import atexit
import json
import socket
import sys
import traceback
import threading
import urllib.request
import urllib.error
from datetime import datetime, timezone
from typing import Any


class PulseRadar:
    """Thread-safe PulseRadar client with optional batching."""

    def __init__(
        self,
        api_key: str,
        *,
        server_id: str | None = None,
        ingest_url: str = "https://ingest.pulseradar.cloud",
        source: str = "python",
        host: str | None = None,
        batch_size: int = 50,
        flush_interval: float = 5.0,
    ) -> None:
        if not api_key:
            raise ValueError("api_key is required")

        self._api_key   = api_key
        self._server_id = server_id
        self._url       = ingest_url.rstrip("/") + "/v1/logs"
        self._source    = source
        self._host      = host or socket.gethostname()
        self._batch     = batch_size
        self._queue: list[dict] = []
        self._lock     = threading.Lock()

        if flush_interval > 0:
            self._timer = _RepeatTimer(flush_interval, self.flush)
            self._timer.daemon = True
            self._timer.start()
        else:
            self._timer = None

        atexit.register(self.flush)

    # ── public API ────────────────────────────────────────────────────────────

    def debug(self, message: str, **fields: Any) -> None:
        self._enqueue("debug", message, fields)

    def info(self, message: str, **fields: Any) -> None:
        self._enqueue("info", message, fields)

    def warning(self, message: str, **fields: Any) -> None:
        self._enqueue("warning", message, fields)

    def error(self, message: str, **fields: Any) -> None:
        self._enqueue("error", message, fields)

    def critical(self, message: str, **fields: Any) -> None:
        self._enqueue("critical", message, fields)

    def log(self, level: str, message: str, **fields: Any) -> None:
        self._enqueue(level, message, fields)

    def capture_exception(
        self,
        exc: BaseException | None = None,
        **fields: Any,
    ) -> None:
        """Capture an exception as an error log with type and stack trace.

        If *exc* is None, the current exception from sys.exc_info() is used.
        """
        if exc is None:
            exc = sys.exc_info()[1]
        if exc is None:
            return
        tb = "".join(traceback.format_exception(type(exc), exc, exc.__traceback__, limit=5))
        self.error(str(exc), exception_type=type(exc).__name__, stack=tb.strip(), **fields)

    def flush(self) -> None:
        with self._lock:
            batch, self._queue = self._queue, []
        if batch:
            self._send(batch)

    def close(self) -> None:
        if self._timer:
            self._timer.cancel()
        self.flush()

    # ── internals ─────────────────────────────────────────────────────────────

    def _enqueue(self, level: str, message: str, fields: dict[str, Any]) -> None:
        entry = {
            "level":     level,
            "message":   message,
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "source":    self._source,
            "host":      self._host,
            "fields":    {k: str(v) for k, v in fields.items()},
        }
        flush_batch: list[dict] = []
        with self._lock:
            self._queue.append(entry)
            if len(self._queue) >= self._batch:
                flush_batch, self._queue = self._queue, []
        if flush_batch:
            self._send(flush_batch)

    def _send(self, entries: list[dict]) -> None:
        body    = json.dumps(entries).encode()
        headers = {
            "Content-Type":  "application/json",
            "Authorization": f"Bearer {self._api_key}",
            "User-Agent":    "pulseradar-python/1.0",
        }
        if self._server_id:
            headers["X-Server-Id"] = self._server_id
        req = urllib.request.Request(
            self._url,
            data=body,
            headers=headers,
            method="POST",
        )
        try:
            with urllib.request.urlopen(req, timeout=3):
                pass
        except Exception:
            pass  # never raise in a logger


class _RepeatTimer(threading.Timer):
    def run(self) -> None:
        while not self.finished.wait(self.interval):
            self.function(*self.args, **self.kwargs)
