"""OTLPExporter: exports LogRecords to an OpenTelemetry Collector.

Supports gRPC and HTTP protocols with batch export, exponential backoff retry,
and graceful fallback to stdout when the SDK is unavailable or no endpoint is
configured.

Requirements: 9.1-9.6
"""

from __future__ import annotations

import json
import sys
import time
from dataclasses import dataclass, field
from typing import Dict, List, Optional

from .log_formatter import LogFormatter
from .log_record import LogRecord


@dataclass
class ExporterConfig:
    """Configuration for OTLPExporter.

    Attributes:
        endpoint:          OTLP collector endpoint URL (e.g. "http://localhost:4317").
                           When empty, falls back to console (stdout) mode.
        protocol:          Transport protocol — "grpc" or "http" (default "grpc").
        headers:           Optional dict of HTTP/gRPC headers (e.g. auth tokens).
        tls_cert_path:     Optional path to a TLS certificate file.
        batch_size:        Maximum number of records per export batch (default 512).
        flush_interval_ms: Interval in milliseconds between automatic flushes (default 5000).
        max_retries:       Maximum number of retry attempts on failure (default 3).
        retry_backoff_ms:  Base backoff in milliseconds for exponential retry (default 1000).
    """

    endpoint: str = ""
    protocol: str = "grpc"
    headers: Optional[Dict[str, str]] = None
    tls_cert_path: Optional[str] = None
    batch_size: int = 512
    flush_interval_ms: int = 5000
    max_retries: int = 3
    retry_backoff_ms: int = 1000


class OTLPExporter:
    """Exports LogRecords to an OpenTelemetry Collector via OTLP.

    When an endpoint is configured, attempts to use the OTel SDK exporters
    (opentelemetry-exporter-otlp-proto-grpc for gRPC,
    opentelemetry-exporter-otlp-proto-http for HTTP).

    If the SDK is unavailable or no endpoint is configured, falls back to
    writing JSON-serialized records to stdout (console-only mode).

    Retry behaviour:
        On export failure, retries up to ``config.max_retries`` times with
        exponential backoff starting at ``config.retry_backoff_ms`` ms.
        After exhausting retries, records are dropped and a warning is written
        to stderr.  The exporter never raises exceptions to the caller.
    """

    def __init__(self, config: Optional[ExporterConfig] = None) -> None:
        self._config = config or ExporterConfig()
        self._formatter = LogFormatter()
        self._pending: List[LogRecord] = []
        self._sdk_exporter = None  # lazy-initialised on first export

        # Attempt to initialise the SDK exporter if an endpoint is provided.
        if self._config.endpoint:
            self._sdk_exporter = self._init_sdk_exporter()

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def export(self, records: List[LogRecord]) -> bool:
        """Export a list of LogRecords, batching by configured batch_size.

        Returns True when all batches were exported successfully, False if any
        batch failed after exhausting retries (records are dropped in that case).
        """
        if not records:
            return True

        all_ok = True
        # Split into batches of at most batch_size.
        for i in range(0, len(records), self._config.batch_size):
            batch = records[i : i + self._config.batch_size]
            ok = self._export_batch(batch)
            if not ok:
                all_ok = False
        return all_ok

    def shutdown(self) -> None:
        """Flush pending records and shut down the exporter."""
        self.force_flush()
        if self._sdk_exporter is not None:
            try:
                self._sdk_exporter.shutdown()
            except Exception:
                pass

    def force_flush(self) -> None:
        """Immediately flush any pending records."""
        if self._pending:
            pending = list(self._pending)
            self._pending.clear()
            self.export(pending)

    # ------------------------------------------------------------------
    # Internal helpers
    # ------------------------------------------------------------------

    def _export_batch(self, batch: List[LogRecord]) -> bool:
        """Export a single batch with retry/backoff.  Returns True on success."""
        if self._sdk_exporter is not None:
            return self._export_via_sdk(batch)
        # Console fallback: write JSON to stdout.
        self._console_export(batch)
        return True

    def _export_via_sdk(self, batch: List[LogRecord]) -> bool:
        """Try to export via the SDK exporter with exponential backoff."""
        backoff_ms = self._config.retry_backoff_ms
        for attempt in range(self._config.max_retries + 1):
            try:
                result = self._sdk_exporter.export(batch)
                # OTel SDK returns ExportResult enum; SUCCESS == 0.
                if hasattr(result, "value"):
                    if result.value == 0:
                        return True
                    # Non-success result — retry.
                else:
                    # If no value attribute, treat as success.
                    return True
            except Exception as exc:
                if attempt == self._config.max_retries:
                    sys.stderr.write(
                        f"[OTLPExporter] Export failed after {self._config.max_retries} retries, "
                        f"dropping {len(batch)} records. Last error: {exc}\n"
                    )
                    return False
            if attempt < self._config.max_retries:
                time.sleep(backoff_ms / 1000.0)
                backoff_ms *= 2  # exponential backoff
        # Exhausted retries.
        sys.stderr.write(
            f"[OTLPExporter] Export failed after {self._config.max_retries} retries, "
            f"dropping {len(batch)} records.\n"
        )
        return False

    def _console_export(self, batch: List[LogRecord]) -> None:
        """Write JSON-serialized records to stdout (console-only mode)."""
        for record in batch:
            try:
                json_line = self._formatter.serialize(record)
                sys.stdout.write(json_line + "\n")
            except Exception as exc:
                sys.stderr.write(f"[OTLPExporter] Serialization error: {exc}\n")

    def _init_sdk_exporter(self):
        """Attempt to initialise an OTel SDK exporter.

        Returns the exporter instance on success, or None if the SDK is not
        available (console-only fallback).
        """
        protocol = self._config.protocol.lower()
        endpoint = self._config.endpoint
        headers = self._config.headers or {}

        if protocol == "grpc":
            return self._init_grpc_exporter(endpoint, headers)
        else:
            return self._init_http_exporter(endpoint, headers)

    def _init_grpc_exporter(self, endpoint: str, headers: Dict[str, str]):
        try:
            from opentelemetry.exporter.otlp.proto.grpc._log_exporter import (
                OTLPLogExporter,
            )
            return OTLPLogExporter(endpoint=endpoint, headers=headers)
        except ImportError:
            sys.stderr.write(
                "[OTLPExporter] opentelemetry-exporter-otlp-proto-grpc not available; "
                "falling back to console mode.\n"
            )
            return None

    def _init_http_exporter(self, endpoint: str, headers: Dict[str, str]):
        try:
            from opentelemetry.exporter.otlp.proto.http._log_exporter import (
                OTLPLogExporter,
            )
            return OTLPLogExporter(endpoint=endpoint, headers=headers)
        except ImportError:
            sys.stderr.write(
                "[OTLPExporter] opentelemetry-exporter-otlp-proto-http not available; "
                "falling back to console mode.\n"
            )
            return None
