// OTLP (OpenTelemetry Protocol) Exporter
// Sends spans to an OpenTelemetry Collector via HTTP+JSON

///|
pub(open) trait JsonHttpClientProvider {
  get_json_client(Self) -> &JsonHttpClient
}

///|
pub(open) trait ProtobufHttpClientProvider {
  get_protobuf_client(Self) -> &ProtobufHttpClient
}

///|
pub(all) struct HttpJsonSendAdaptor {
  client_provider : &JsonHttpClientProvider
  endpoint : String
  headers : Map[String, String]
}

///|
pub fn HttpJsonSendAdaptor::new(
  client_provider : &JsonHttpClientProvider,
  endpoint? : String = "http://localhost:4318/v1/traces",
  headers? : Map[String, String] = {},
) -> HttpJsonSendAdaptor {
  { client_provider, endpoint, headers }
}

///|
pub(all) struct HttpProtobufSendAdaptor {
  client_provider : &ProtobufHttpClientProvider
  endpoint : String
  headers : Map[String, String]
}

///|
pub fn HttpProtobufSendAdaptor::new(
  client_provider : &ProtobufHttpClientProvider,
  endpoint? : String = "http://localhost:4318/v1/traces",
  headers? : Map[String, String] = {},
) -> HttpProtobufSendAdaptor {
  { client_provider, endpoint, headers }
}

///|
pub(all) struct StdoutSendAdaptor {
  print : (String) -> Unit
}

///|
pub fn StdoutSendAdaptor::new(print : (String) -> Unit) -> StdoutSendAdaptor {
  StdoutSendAdaptor::{ print, }
}

///|
pub(all) enum ExportAdapter {
  HttpJson(HttpJsonSendAdaptor)
  HttpProtobuf(HttpProtobufSendAdaptor)
  Stdout(StdoutSendAdaptor)
}

///|
pub struct OTLPExporterOptions {
  logger_factory : () -> InternalLogger
  export_adapter : ExportAdapter
}

///|
pub struct OTLPExporter {
  config : OTLPExporterOptions
  priv logger : InternalLogger
  priv export_adapter : ExportAdapter
}

///|
pub(open) trait JsonHttpClient {
  async http_post_json(
    Self,
    url : String,
    body : String,
    headers : Map[String, String],
  ) -> Result[HttpResponse, HttpError]
}

///|
pub(open) trait ProtobufHttpClient {
  async http_post_protobuf(
    Self,
    url : String,
    body : Bytes,
    headers : Map[String, String],
  ) -> Result[HttpResponse, HttpError]
}

// Create default OTLP exporter configuration

///|
pub fn OTLPExporterOptions::default(
  export_adapter : ExportAdapter,
) -> OTLPExporterOptions {
  { logger_factory: () => InternalLogger::none(), export_adapter }
}

///|
pub fn OTLPExporterOptions::stdout(
  print : (String) -> Unit,
) -> OTLPExporterOptions {
  OTLPExporterOptions::default(
    ExportAdapter::Stdout(StdoutSendAdaptor::new(print)),
  )
}

///|
pub fn OTLPExporterOptions::http_json(
  client_provider : &JsonHttpClientProvider,
  endpoint? : String = "http://localhost:4318/v1/traces",
  headers? : Map[String, String] = {},
) -> OTLPExporterOptions {
  OTLPExporterOptions::default(
    ExportAdapter::HttpJson(
      HttpJsonSendAdaptor::new(client_provider, endpoint~, headers~),
    ),
  )
}

///|
pub fn OTLPExporterOptions::http_protobuf(
  client_provider : &ProtobufHttpClientProvider,
  endpoint? : String = "http://localhost:4318/v1/traces",
  headers? : Map[String, String] = {},
) -> OTLPExporterOptions {
  OTLPExporterOptions::default(
    ExportAdapter::HttpProtobuf(
      HttpProtobufSendAdaptor::new(client_provider, endpoint~, headers~),
    ),
  )
}

// Set the endpoint URL
// Set the timeout in milliseconds

///|
pub fn OTLPExporterOptions::enable_debug_log(
  self : OTLPExporterOptions,
  print~ : (&Show) -> Unit,
) -> OTLPExporterOptions {
  { ..self, logger_factory: () => InternalLogger::{ print_fn: print } }
}

// Create a new OTLP exporter with the given configuration

///|
pub fn OTLPExporter::new(config : OTLPExporterOptions) -> OTLPExporter {
  {
    config,
    logger: (config.logger_factory)(),
    export_adapter: config.export_adapter,
  }
}

// Explicit implementation of SpanExporter trait for OTLPExporter

///|
pub async fn OTLPExporter::export_spans(
  self : OTLPExporter,
  spans : Array[Span],
) -> Unit {
  if spans.length() == 0 {
    return
  }
  let adaptor = self.export_adapter
  match adaptor {
    ExportAdapter::HttpJson(http_adaptor) => {
      let json_body = spans_to_resource_spans_json(spans)
      let client = http_adaptor.client_provider.get_json_client()
      self.export_spans_by_json_http_client(
        json_body, client, http_adaptor, spans,
      )
    }
    ExportAdapter::HttpProtobuf(http_adaptor) => {
      let protobuf_body = spans_to_resource_spans_protobuf_bytes(spans) catch {
        err => {
          self.logger.log(
            "[OTLP] Failed to serialize spans to Protobuf: " + err.to_string(),
          )
          return
        }
      }
      let client = http_adaptor.client_provider.get_protobuf_client()
      self.export_spans_by_protobuf_http_client(
        protobuf_body, client, http_adaptor, spans,
      )
    }
    ExportAdapter::Stdout(stdout_adaptor) =>
      (stdout_adaptor.print)(spans_to_resource_spans_json(spans))
  }
}

///|
pub async fn OTLPExporter::export_spans_by_json_http_client(
  self : OTLPExporter,
  json_body : String,
  http_client : &JsonHttpClient,
  http_adaptor : HttpJsonSendAdaptor,
  spans : Array[Span],
) -> Unit {
  self.logger.log(
    "[OTLP] Starting export of " +
    spans.length().to_string() +
    " span(s) as JSON",
  )
  try {
    let result = http_client.http_post_json(
      http_adaptor.endpoint,
      json_body,
      http_adaptor.headers.merge({ "Content-Type": "application/json" }),
    )
    match result {
      Ok(response) =>
        if response.status_code >= 200 && response.status_code < 300 {
          // Success - span exported successfully
          self.logger.log(
            "[OTLP] Exported " + spans.length().to_string() + " span(s) as JSON",
          )
        } else {
          // HTTP error response - already handled by HttpStatusError
          self.logger.log(
            "[OTLP] Export failed with status " +
            response.status_code.to_string() +
            ": " +
            response.body,
          )
        }
      Err(error) => {
        // Classify and log error
        let error_type = match error {
          NetworkError(_) => "Network"
          RequestError(_) => "Request"
          HttpStatusError(_, _) => "HTTP Status"
          InvalidResponse(_) => "Invalid Response"
          TimeoutError(_) => "Timeout"
          SecurityError(_) => "Security"
        }
        self.logger.log(
          "[OTLP] " +
          error_type +
          " error: " +
          error.to_string() +
          (if error.is_retryable() { " (retryable)" } else { "" }),
        )
      }
    }
  } catch {
    err => self.logger.log("[OTLP] HTTP JSON export error: " + err.to_string())
  }
}

///|
pub async fn OTLPExporter::export_spans_by_protobuf_http_client(
  self : OTLPExporter,
  protobuf_body : Bytes,
  http_client : &ProtobufHttpClient,
  http_adaptor : HttpProtobufSendAdaptor,
  spans : Array[Span],
) -> Unit {
  self.logger.log(
    "[OTLP] Starting export of " +
    spans.length().to_string() +
    " span(s) as Protobuf",
  )
  try {
    let result = http_client.http_post_protobuf(
      http_adaptor.endpoint,
      protobuf_body,
      http_adaptor.headers.merge({ "Content-Type": "application/x-protobuf" }),
    )
    match result {
      Ok(response) =>
        if response.status_code >= 200 && response.status_code < 300 {
          // Success - span exported successfully
          self.logger.log(
            "[OTLP] Exported " +
            spans.length().to_string() +
            " span(s) as Protobuf",
          )
        } else {
          // HTTP error response - already handled by HttpStatusError
          self.logger.log(
            "[OTLP] Export failed with status " +
            response.status_code.to_string() +
            ": " +
            response.body,
          )
        }
      Err(error) => {
        // Classify and log error
        let error_type = match error {
          NetworkError(_) => "Network"
          RequestError(_) => "Request"
          HttpStatusError(_, _) => "HTTP Status"
          InvalidResponse(_) => "Invalid Response"
          TimeoutError(_) => "Timeout"
          SecurityError(_) => "Security"
        }
        self.logger.log(
          "[OTLP] " +
          error_type +
          " error: " +
          error.to_string() +
          (if error.is_retryable() { " (retryable)" } else { "" }),
        )
      }
    }
  } catch {
    err =>
      self.logger.log("[OTLP] HTTP Protobuf export error: " + err.to_string())
  }
}

// Explicit implementation of SpanExporter trait for OTLPExporter

///|
pub fn OTLPExporter::shutdown(self : OTLPExporter) -> Unit {
  self.logger.log("[OTLP] OTLPExporter shutdown")
}

///|
pub impl SpanExporter for OTLPExporter with export_spans(
  self : OTLPExporter,
  spans : Array[Span],
) -> Unit {
  self.logger.log("[OTLP] Starting export of spans asynchronously")
  self.export_spans(spans) catch {
    err => self.logger.log("[OTLP] Error during export: " + err.to_string())
  }
}

///|
///[]
pub impl SpanExporter for OTLPExporter with shutdown(self : OTLPExporter) -> Unit {
  self.shutdown()
}