//! Observability infrastructure for the Foxhunt HFT trading system.
//!
//! This module provides comprehensive observability capabilities including:
//! - Correlation ID generation and propagation across service boundaries
//! - Structured JSON logging with consistent format
//! - OpenTelemetry distributed tracing with OTLP export
//!
//! # Architecture
//!
//! The observability infrastructure follows a three-layer approach:
//!
//! 1. **Correlation Layer**: Generates and propagates correlation IDs across all services
//!    via gRPC metadata, enabling request tracking across the entire system.
//!
//! 2. **Logging Layer**: Provides structured JSON logging with automatic correlation ID
//!    injection, service identification, and log rotation.
//!
//! 3. **Tracing Layer**: Implements OpenTelemetry distributed tracing with parent-child
//!    span linking, context propagation, and OTLP export (compatible with Jaeger,
//!    Grafana Tempo, Datadog, and other OTLP-compatible backends).
//!
//! # Usage
//!
//! ## Initialize Observability in Services
//!
//! ```rust,no_run
//! use common::observability::{init_observability, CorrelationId};
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//!     // Initialize observability infrastructure
//!     init_observability("trading_service", Some("http://localhost:4317"))?;
//!
//!     // Correlation IDs are automatically propagated via gRPC metadata
//!     Ok(())
//! }
//! ```
//!
//! ## Log with Correlation ID
//!
//! ```rust,no_run
//! use tracing::{info, error};
//! use common::observability::CorrelationId;
//!
//! async fn handle_request() {
//!     let correlation_id = CorrelationId::new();
//!
//!     info!(
//!         correlation_id = %correlation_id,
//!         "Processing trade request"
//!     );
//! }
//! ```
//!
//! ## Create Traced Spans
//!
//! ```rust,no_run
//! use tracing::{info_span, instrument};
//!
//! #[instrument(name = "process_order", skip(order))]
//! async fn process_order(order: Order) -> Result<(), Error> {
//!     // Span is automatically created and linked to parent context
//!     Ok(())
//! }
//! ```

pub mod correlation;
pub mod logger;
pub mod tracing_config;

#[cfg(test)]
mod tests;

// Re-export commonly used types
pub use correlation::{CorrelationId, CorrelationIdExt, CORRELATION_ID_HEADER};
pub use logger::{init_json_logger, JsonLoggerConfig, LogLevel};
pub use tracing_config::{build_otel_tracer, init_tracing, TracingConfig};

use crate::error::CommonResult;

/// Initialize the complete observability stack for a service.
///
/// This is a convenience function that initializes both logging and tracing
/// in a single call. It should be called early in the service startup process,
/// before any business logic executes.
///
/// # Arguments
///
/// * `service_name` - Name of the service (e.g., "api", "trading_service")
/// * `otlp_endpoint` - Optional OTLP collector endpoint (e.g., `Some("http://localhost:4317")`).
///   When `None`, the OTLP tracing layer is disabled and only JSON logging is active.
///
/// # Returns
///
/// Returns `Ok(())` on success, or an error if initialization fails.
///
/// # Example
///
/// ```rust,no_run
/// use common::observability::init_observability;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
///     init_observability("trading_service", Some("http://localhost:4317"))?;
///
///     // Service logic here...
///     Ok(())
/// }
/// ```
pub fn init_observability(
    service_name: &str,
    otlp_endpoint: Option<&str>,
) -> CommonResult<()> {
    use tracing_subscriber::prelude::*;

    // Build env filter: honour RUST_LOG, default to info.
    let env_filter = tracing_subscriber::EnvFilter::try_from_default_env()
        .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info"));

    // JSON fmt layer — stdout captured by containerd / Promtail → Loki.
    let fmt_layer = tracing_subscriber::fmt::layer()
        .json()
        .with_target(true)
        .with_current_span(true)
        .with_span_list(false);

    // OTLP layer — gracefully degrade if Tempo is unavailable.
    // build_otel_tracer returns Option<Tracer>; wrapping with
    // tracing_opentelemetry::layer() inside the subscriber chain lets
    // the `S` type parameter be inferred correctly by the compiler.
    let tracing_config = TracingConfig {
        service_name: service_name.to_string(),
        otlp_endpoint: otlp_endpoint.unwrap_or_default().to_string(),
        enable_export: otlp_endpoint.is_some(),
    };
    let otel_tracer = tracing_config::build_otel_tracer(&tracing_config);
    let otel_layer = otel_tracer
        .map(|tracer| tracing_opentelemetry::layer().with_tracer(tracer));
    if otel_layer.is_none() {
        eprintln!("OTLP tracing layer unavailable — logs-only mode");
    }

    // Single subscriber with all layers. `.with(Option<L>)` is a no-op when None.
    tracing_subscriber::registry()
        .with(env_filter)
        .with(fmt_layer)
        .with(otel_layer)
        .init();

    Ok(())
}
