use crate::layers::BoxedLayer;
use clap::ValueEnum;
use std::{fmt, fmt::Display};
use tracing_appender::non_blocking::NonBlocking;
use tracing_subscriber::{layer::Filter, Layer, Registry};

/// Represents the logging format.
///
/// This enum defines the supported formats for logging output.
/// It is used to configure the format layer of a tracing subscriber.
#[derive(Debug, Copy, Clone, ValueEnum, Eq, PartialEq)]
pub enum LogFormat {
    /// Represents JSON formatting for logs.
    /// This format outputs log records as JSON objects,
    /// making it suitable for structured logging.
    Json,

    /// Represents logfmt (key=value) formatting for logs.
    /// This format is concise and human-readable,
    /// typically used in command-line applications.
    LogFmt,

    /// Represents terminal-friendly formatting for logs.
    Terminal,
}

impl LogFormat {
    /// Applies the specified logging format to create a new layer.
    ///
    /// # Arguments
    /// * `filter` - A filter layer (either `EnvFilter` or a `reload::Layer` wrapping one).
    /// * `color` - An optional string that enables or disables ANSI color codes in the logs.
    /// * `show_target` - Whether to show the target module path in log output.
    /// * `file_writer` - An optional [`NonBlocking`] writer for directing logs to a file. When
    ///   `None`, logs are written to stdout.
    ///
    /// # Returns
    /// A `BoxedLayer<Registry>` that can be added to a tracing subscriber.
    pub fn apply<F>(
        &self,
        filter: F,
        color: Option<String>,
        show_target: bool,
        file_writer: Option<NonBlocking>,
    ) -> BoxedLayer<Registry>
    where
        F: Filter<Registry> + Send + Sync + 'static,
    {
        let ansi = if let Some(color) = color {
            std::env::var("RUST_LOG_STYLE").map(|val| val != "never").unwrap_or(color != "never")
        } else {
            false
        };

        let target = std::env::var("RUST_LOG_TARGET").map(|val| val != "0").unwrap_or(show_target);

        match self {
            Self::Json => {
                let layer =
                    tracing_subscriber::fmt::layer().json().with_ansi(ansi).with_target(target);
                if let Some(writer) = file_writer {
                    layer.with_writer(writer).with_filter(filter).boxed()
                } else {
                    layer.with_filter(filter).boxed()
                }
            }
            Self::LogFmt => {
                let layer = tracing_logfmt::builder().layer();
                if let Some(writer) = file_writer {
                    layer.with_writer(writer).with_filter(filter).boxed()
                } else {
                    layer.with_filter(filter).boxed()
                }
            }
            Self::Terminal => {
                let layer = tracing_subscriber::fmt::layer().with_ansi(ansi).with_target(target);
                if let Some(writer) = file_writer {
                    layer.with_writer(writer).with_filter(filter).boxed()
                } else {
                    layer.with_filter(filter).boxed()
                }
            }
        }
    }
}

impl Display for LogFormat {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Json => write!(f, "json"),
            Self::LogFmt => write!(f, "logfmt"),
            Self::Terminal => write!(f, "terminal"),
        }
    }
}
