/*
    This file is part of jellything (https://codeberg.org/metamuffin/jellything)
    which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
    Copyright (C) 2026 metamuffin <metamuffin.org>
*/

use jellycommon::{
    api::{LogLevel, LogLine},
    chrono::Utc,
};
use log::Level;
use std::{
    collections::VecDeque,
    sync::{Arc, LazyLock, RwLock},
};
use tokio::sync::broadcast;

const MAX_LOG_LEN: usize = 4096;

static LOGGER: LazyLock<Log> = LazyLock::new(Log::default);

pub fn enable_logging() {
    log::set_logger(&*LOGGER).unwrap();
    log::set_max_level(log::LevelFilter::Debug);
}

type LogBuffer = VecDeque<Arc<LogLine>>;

pub struct Log {
    inner: env_logger::Logger,
    stream: (
        broadcast::Sender<Arc<LogLine>>,
        broadcast::Sender<Arc<LogLine>>,
    ),
    log: RwLock<(LogBuffer, LogBuffer)>,
}

pub fn get_log_buffer(warn: bool) -> VecDeque<Arc<LogLine>> {
    if warn {
        LOGGER.log.read().unwrap().1.clone()
    } else {
        LOGGER.log.read().unwrap().0.clone()
    }
}
pub fn get_log_stream(warn: bool) -> broadcast::Receiver<Arc<LogLine>> {
    if warn {
        LOGGER.stream.1.subscribe()
    } else {
        LOGGER.stream.0.subscribe()
    }
}

impl Default for Log {
    fn default() -> Self {
        Self {
            inner: env_logger::builder()
                .filter_level(log::LevelFilter::Warn)
                .parse_env("LOG")
                .build(),
            stream: (
                tokio::sync::broadcast::channel(1024).0,
                tokio::sync::broadcast::channel(1024).0,
            ),
            log: Default::default(),
        }
    }
}
impl Log {
    fn should_log(&self, metadata: &log::Metadata) -> bool {
        let level = metadata.level();
        level
            <= match metadata.target() {
                x if x.starts_with("jelly") => Level::Debug,
                x if x.starts_with("rocket::") => Level::Info,
                _ => Level::Warn,
            }
    }
    fn do_log(&self, record: &log::Record) {
        let time = Utc::now();
        let line = Arc::new(LogLine {
            time,
            module: record.module_path_static(),
            level: match record.level() {
                Level::Error => LogLevel::Error,
                Level::Warn => LogLevel::Warn,
                Level::Info => LogLevel::Info,
                Level::Debug => LogLevel::Debug,
                Level::Trace => LogLevel::Trace,
            },
            message: record.args().to_string(),
        });
        let mut w = self.log.write().unwrap();
        w.0.push_back(line.clone());
        let _ = self.stream.0.send(line.clone());
        while w.0.len() > MAX_LOG_LEN {
            w.0.pop_front();
        }
        if record.level() <= Level::Warn {
            let _ = self.stream.1.send(line.clone());
            w.1.push_back(line);
            while w.1.len() > MAX_LOG_LEN {
                w.1.pop_front();
            }
        }
    }
}

impl log::Log for Log {
    fn enabled(&self, metadata: &log::Metadata) -> bool {
        self.inner.enabled(metadata) || self.should_log(metadata)
    }
    fn log(&self, record: &log::Record) {
        match (record.module_path_static(), record.line()) {
            // TODO is there a better way to ignore those?
            (Some("rocket::rocket"), Some(670)) => return,
            (Some("rocket::server"), Some(401)) => return,
            _ => {}
        }
        if self.inner.enabled(record.metadata()) {
            self.inner.log(record);
        }
        if self.should_log(record.metadata()) {
            self.do_log(record)
        }
    }
    fn flush(&self) {
        self.inner.flush();
    }
}
