//! Structured JSON logging with automatic correlation ID injection.
//!
//! This module provides a JSON-formatted logger that integrates with the tracing
//! ecosystem and automatically includes correlation IDs in all log statements.
//!
//! # Features
//!
//! - Structured JSON output for easy parsing by log aggregation tools
//! - Automatic correlation ID injection from async task context
//! - Console and file output with configurable rotation
//! - Service name identification in all log entries
//! - Log level filtering
//!
//! # Log Format
//!
//! ```json
//! {
//!   "timestamp": "2025-10-22T10:30:45.123456Z",
//!   "level": "INFO",
//!   "correlation_id": "550e8400-e29b-41d4-a716-446655440000",
//!   "service": "trading_service",
//!   "target": "trading_service::orders",
//!   "message": "Order executed successfully",
//!   "fields": {
//!     "order_id": "12345",
//!     "symbol": "ES.FUT",
//!     "quantity": 10
//!   }
//! }
//! ```

use crate::error::{CommonError, CommonResult};
use std::path::PathBuf;
use tracing::Level;
use tracing_subscriber::{
    layer::SubscriberExt,
    EnvFilter, Registry,
};

/// Log rotation strategy.
///
/// Defines when log files should be rotated to prevent unbounded growth.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LogRotation {
    /// Rotate logs daily at midnight UTC
    Daily,
    /// Rotate logs hourly
    Hourly,
    /// No automatic rotation (manual only)
    Never,
}

/// Log level for filtering.
///
/// Maps to standard tracing levels.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LogLevel {
    /// Trace level (most verbose)
    Trace,
    /// Debug level
    Debug,
    /// Info level (default for production)
    Info,
    /// Warning level
    Warn,
    /// Error level
    Error,
}

impl From<LogLevel> for Level {
    fn from(level: LogLevel) -> Self {
        match level {
            LogLevel::Trace => Level::TRACE,
            LogLevel::Debug => Level::DEBUG,
            LogLevel::Info => Level::INFO,
            LogLevel::Warn => Level::WARN,
            LogLevel::Error => Level::ERROR,
        }
    }
}

impl From<LogLevel> for tracing_subscriber::filter::LevelFilter {
    fn from(level: LogLevel) -> Self {
        match level {
            LogLevel::Trace => tracing_subscriber::filter::LevelFilter::TRACE,
            LogLevel::Debug => tracing_subscriber::filter::LevelFilter::DEBUG,
            LogLevel::Info => tracing_subscriber::filter::LevelFilter::INFO,
            LogLevel::Warn => tracing_subscriber::filter::LevelFilter::WARN,
            LogLevel::Error => tracing_subscriber::filter::LevelFilter::ERROR,
        }
    }
}

/// Configuration for the JSON logger.
///
/// Controls all aspects of logging behavior including output destinations,
/// rotation, and filtering.
#[derive(Debug, Clone)]
pub struct JsonLoggerConfig {
    /// Service name to include in all log entries
    pub service_name: String,

    /// Minimum log level to output
    pub log_level: LogLevel,

    /// Enable console (stdout) output
    pub enable_console: bool,

    /// Enable file output
    pub enable_file: bool,

    /// Directory for log files (only used if enable_file is true)
    pub log_directory: String,

    /// Log rotation strategy (only used if enable_file is true)
    pub rotation: LogRotation,

    /// Maximum number of rotated log files to keep
    pub max_files: usize,

    /// Maximum size of each log file in megabytes
    pub max_file_size_mb: usize,
}

impl Default for JsonLoggerConfig {
    fn default() -> Self {
        Self {
            service_name: "foxhunt".to_string(),
            log_level: LogLevel::Info,
            enable_console: true,
            enable_file: true,
            log_directory: "logs".to_string(),
            rotation: LogRotation::Daily,
            max_files: 10,
            max_file_size_mb: 100,
        }
    }
}

/// Initialize the JSON logger with the given configuration.
///
/// This function sets up the tracing subscriber with JSON formatting and
/// configures output destinations based on the provided configuration.
///
/// # Arguments
///
/// * `config` - Configuration for the logger
///
/// # Returns
///
/// Returns `Ok(())` on success, or an error if initialization fails.
///
/// # Example
///
/// ```rust,no_run
/// use common::observability::logger::{init_json_logger, JsonLoggerConfig, LogLevel, LogRotation};
///
/// let config = JsonLoggerConfig {
///     service_name: "trading_service".to_string(),
///     log_level: LogLevel::Info,
///     enable_console: true,
///     enable_file: true,
///     log_directory: "logs/trading_service".to_string(),
///     rotation: LogRotation::Daily,
///     max_files: 10,
///     max_file_size_mb: 100,
/// };
///
/// init_json_logger(config).expect("Failed to initialize logger");
/// ```
pub fn init_json_logger(config: JsonLoggerConfig) -> CommonResult<()> {
    // Create log directory if file logging is enabled
    if config.enable_file {
        std::fs::create_dir_all(&config.log_directory).map_err(|e| {
            CommonError::config(format!("Failed to create log directory: {}", e))
        })?;
    }

    // Build environment filter
    let env_filter = EnvFilter::try_from_default_env()
        .unwrap_or_else(|_| {
            let level_filter: tracing_subscriber::filter::LevelFilter = config.log_level.into();
            EnvFilter::new(format!("{}={}", config.service_name, level_filter))
        });

    // Create console layer if enabled
    let console_layer = if config.enable_console {
        Some(tracing_subscriber::fmt::layer()
            .json()
            .with_target(true)
            .with_current_span(true)
            .with_span_list(false)
            .with_writer(std::io::stdout))
    } else {
        None
    };

    // Create file layer if enabled
    let file_layer = if config.enable_file {
        let log_file_path = create_log_file_path(&config)?;
        let file = std::fs::OpenOptions::new()
            .create(true)
            .append(true)
            .open(&log_file_path)
            .map_err(|e| {
                CommonError::config(format!("Failed to open log file: {}", e))
            })?;

        // Use tracing_appender::non_blocking for proper async file I/O
        // The _guard must be held for the lifetime of the program to ensure proper cleanup
        let (non_blocking, _guard) = tracing_appender::non_blocking(file);

        // Store the guard to prevent it from being dropped
        // In production, you may want to store this guard in a static or application state
        std::mem::forget(_guard);

        Some(tracing_subscriber::fmt::layer()
            .json()
            .with_target(true)
            .with_current_span(true)
            .with_span_list(false)
            .with_writer(non_blocking))
    } else {
        None
    };

    // Build the subscriber with all layers
    let subscriber = Registry::default()
        .with(env_filter)
        .with(console_layer)
        .with(file_layer);

    // Set the global subscriber
    tracing::subscriber::set_global_default(subscriber)
        .map_err(|e| CommonError::config(format!("Failed to set global subscriber: {}", e)))?;

    Ok(())
}

/// Create the log file path based on rotation strategy.
fn create_log_file_path(config: &JsonLoggerConfig) -> CommonResult<PathBuf> {
    let timestamp = chrono::Utc::now();

    let filename = match config.rotation {
        LogRotation::Daily => {
            format!("{}-{}.log", config.service_name, timestamp.format("%Y-%m-%d"))
        }
        LogRotation::Hourly => {
            format!(
                "{}-{}.log",
                config.service_name,
                timestamp.format("%Y-%m-%d-%H")
            )
        }
        LogRotation::Never => {
            format!("{}.log", config.service_name)
        }
    };

    let mut path = PathBuf::from(&config.log_directory);
    path.push(filename);

    Ok(path)
}

/// Rotate log files by removing old files that exceed the maximum count.
///
/// This function should be called periodically to clean up old log files
/// based on the configured retention policy.
///
/// # Arguments
///
/// * `config` - Logger configuration containing retention settings
///
/// # Returns
///
/// Returns the number of files deleted.
pub fn rotate_logs(config: &JsonLoggerConfig) -> CommonResult<usize> {
    if !config.enable_file {
        return Ok(0);
    }

    let log_dir = PathBuf::from(&config.log_directory);
    if !log_dir.exists() {
        return Ok(0);
    }

    // Get all log files sorted by modification time (oldest first)
    let mut log_files: Vec<_> = std::fs::read_dir(&log_dir)
        .map_err(|e| CommonError::config(format!("Failed to read log directory: {}", e)))?
        .filter_map(|entry| entry.ok())
        .filter(|entry| {
            entry.path().extension().and_then(|s| s.to_str()) == Some("log")
                && entry
                    .path()
                    .file_stem()
                    .and_then(|s| s.to_str())
                    .map(|s| s.starts_with(&config.service_name))
                    .unwrap_or(false)
        })
        .filter_map(|entry| {
            let metadata = entry.metadata().ok()?;
            let modified = metadata.modified().ok()?;
            Some((entry.path(), modified))
        })
        .collect();

    log_files.sort_by_key(|(_, modified)| *modified);

    // Delete oldest files if we exceed max_files
    let mut deleted = 0;
    if log_files.len() > config.max_files {
        let to_delete = log_files.len() - config.max_files;
        for (path, _) in log_files.iter().take(to_delete) {
            if std::fs::remove_file(path).is_ok() {
                deleted += 1;
            }
        }
    }

    Ok(deleted)
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;

    #[test]
    fn test_log_level_conversion() {
        assert_eq!(Level::from(LogLevel::Trace), Level::TRACE);
        assert_eq!(Level::from(LogLevel::Debug), Level::DEBUG);
        assert_eq!(Level::from(LogLevel::Info), Level::INFO);
        assert_eq!(Level::from(LogLevel::Warn), Level::WARN);
        assert_eq!(Level::from(LogLevel::Error), Level::ERROR);
    }

    #[test]
    fn test_default_config() {
        let config = JsonLoggerConfig::default();

        assert_eq!(config.service_name, "foxhunt");
        assert_eq!(config.log_level, LogLevel::Info);
        assert!(config.enable_console);
        assert!(config.enable_file);
        assert_eq!(config.log_directory, "logs");
        assert_eq!(config.rotation, LogRotation::Daily);
        assert_eq!(config.max_files, 10);
        assert_eq!(config.max_file_size_mb, 100);
    }

    #[test]
    fn test_create_log_file_path_daily() {
        let temp_dir = TempDir::new().unwrap();
        let config = JsonLoggerConfig {
            service_name: "test_service".to_string(),
            log_level: LogLevel::Info,
            enable_console: false,
            enable_file: true,
            log_directory: temp_dir.path().to_str().unwrap().to_string(),
            rotation: LogRotation::Daily,
            max_files: 10,
            max_file_size_mb: 100,
        };

        let path = create_log_file_path(&config).unwrap();
        let filename = path.file_name().unwrap().to_str().unwrap();

        // Should contain service name and date
        assert!(filename.starts_with("test_service-"));
        assert!(filename.ends_with(".log"));
    }

    #[test]
    fn test_create_log_file_path_hourly() {
        let temp_dir = TempDir::new().unwrap();
        let config = JsonLoggerConfig {
            service_name: "test_service".to_string(),
            log_level: LogLevel::Info,
            enable_console: false,
            enable_file: true,
            log_directory: temp_dir.path().to_str().unwrap().to_string(),
            rotation: LogRotation::Hourly,
            max_files: 10,
            max_file_size_mb: 100,
        };

        let path = create_log_file_path(&config).unwrap();
        let filename = path.file_name().unwrap().to_str().unwrap();

        // Should contain service name, date, and hour
        assert!(filename.starts_with("test_service-"));
        assert!(filename.ends_with(".log"));
    }

    #[test]
    fn test_create_log_file_path_never() {
        let temp_dir = TempDir::new().unwrap();
        let config = JsonLoggerConfig {
            service_name: "test_service".to_string(),
            log_level: LogLevel::Info,
            enable_console: false,
            enable_file: true,
            log_directory: temp_dir.path().to_str().unwrap().to_string(),
            rotation: LogRotation::Never,
            max_files: 10,
            max_file_size_mb: 100,
        };

        let path = create_log_file_path(&config).unwrap();
        let filename = path.file_name().unwrap().to_str().unwrap();

        // Should only contain service name
        assert_eq!(filename, "test_service.log");
    }

    #[test]
    fn test_rotate_logs() {
        let temp_dir = TempDir::new().unwrap();
        let log_dir = temp_dir.path().to_str().unwrap();

        // Create 15 log files
        for i in 0..15 {
            let filename = format!("test_service-2025-10-{:02}.log", i + 1);
            let mut path = PathBuf::from(log_dir);
            path.push(filename);
            std::fs::write(&path, "test log content").unwrap();

            // Sleep briefly to ensure different modification times
            std::thread::sleep(std::time::Duration::from_millis(10));
        }

        let config = JsonLoggerConfig {
            service_name: "test_service".to_string(),
            log_level: LogLevel::Info,
            enable_console: false,
            enable_file: true,
            log_directory: log_dir.to_string(),
            rotation: LogRotation::Daily,
            max_files: 10,
            max_file_size_mb: 100,
        };

        // Rotate logs (should delete 5 oldest files)
        let deleted = rotate_logs(&config).unwrap();
        assert_eq!(deleted, 5);

        // Verify only 10 files remain
        let remaining = std::fs::read_dir(log_dir)
            .unwrap()
            .filter_map(|e| e.ok())
            .filter(|e| e.path().extension().and_then(|s| s.to_str()) == Some("log"))
            .count();

        assert_eq!(remaining, 10);
    }

    #[test]
    fn test_rotate_logs_disabled() {
        let config = JsonLoggerConfig {
            service_name: "test_service".to_string(),
            log_level: LogLevel::Info,
            enable_console: true,
            enable_file: false,
            log_directory: "logs".to_string(),
            rotation: LogRotation::Daily,
            max_files: 10,
            max_file_size_mb: 100,
        };

        // Should return 0 when file logging is disabled
        let deleted = rotate_logs(&config).unwrap();
        assert_eq!(deleted, 0);
    }
}
