use crate::config::LogLevel;
use crate::logging::ipc::{AccessRecord, LogEvent};
use crossbeam_channel::Sender;
use std::time::{SystemTime, UNIX_EPOCH};

/// Loger
#[derive(Clone)]
pub struct Logger {
    level: LogLevel,
    tx: Sender<LogEvent>,
    log_stdout: bool,
    access_enabled: bool,
    worker_id: Option<u32>,
}

impl Logger {
    pub fn new(level: LogLevel, tx: Sender<LogEvent>, log_stdout: bool) -> Self {
        Self {
            level,
            tx,
            log_stdout,
            access_enabled: true,
            worker_id: None,
        }
    }

    pub fn with_access_enabled(mut self, access_enabled: bool) -> Self {
        self.access_enabled = access_enabled;
        self
    }

    pub fn with_worker_id(mut self, worker_id: u32) -> Self {
        self.worker_id = Some(worker_id);
        self
    }

    // Check if a level is enabled
    #[inline(always)]
    fn enabled(&self, level: LogLevel) -> bool {
        level as u8 >= self.level as u8
    }

    // Log
    #[inline(always)]
    pub fn log<F>(&self, level: LogLevel, f: F)
    where
        F: FnOnce() -> String,
    {
        if self.enabled(level) {
            let _ = self.tx.try_send(LogEvent::Message {
                level,
                ts_millis: now_millis(),
                pid: std::process::id(),
                worker_id: self.worker_id,
                msg: f(),
            });
        }
    }

    // Trace
    #[inline(always)]
    pub fn trace<F>(&self, f: F)
    where
        F: FnOnce() -> String,
    {
        self.log(LogLevel::Trace, f);
    }

    // Debug
    #[inline(always)]
    pub fn debug<F>(&self, f: F)
    where
        F: FnOnce() -> String,
    {
        self.log(LogLevel::Debug, f);
    }

    // Info
    #[inline(always)]
    pub fn info<F>(&self, f: F)
    where
        F: FnOnce() -> String,
    {
        self.log(LogLevel::Info, f);
    }

    // Warn
    #[inline(always)]
    pub fn warn<F>(&self, f: F)
    where
        F: FnOnce() -> String,
    {
        self.log(LogLevel::Warn, f);
    }

    // Error Log
    #[inline(always)]
    pub fn error<F>(&self, f: F)
    where
        F: FnOnce() -> String,
    {
        self.log(LogLevel::Error, f);
    }

    // Access Log
    #[inline(always)]
    pub fn access_record(&self, record: AccessRecord) {
        if !self.access_enabled {
            return;
        }
        let _ = self.tx.try_send(LogEvent::Access {
            ts_millis: now_millis(),
            pid: std::process::id(),
            worker_id: self.worker_id,
            remote: record.remote,
            method: record.method,
            uri: record.uri,
            version: record.version,
            status: record.status,
            size: record.size,
            duration_micros: record.duration_micros,
        });
    }

    #[inline(always)]
    pub fn worker_stats(&self, active_connections: usize, total_requests: u64) {
        let Some(worker_id) = self.worker_id else {
            return;
        };
        let _ = self.tx.try_send(LogEvent::WorkerStats {
            ts_millis: now_millis(),
            pid: std::process::id(),
            worker_id,
            active_connections,
            total_requests,
        });
    }
}

fn now_millis() -> u64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_millis() as u64
}
