use std::{io::Write, sync::OnceLock};

use crate::{
    events::LogEvent,
    extensions::{AppHandleExt, EyreReportToMessage},
};
use eyre::{OptionExt, WrapErr};
use notify::{RecommendedWatcher, Watcher};
use tauri::{AppHandle, Manager};
use tauri_specta::Event;
use tracing::{Instrument, Level, Subscriber, instrument};
use tracing_appender::{
    non_blocking::WorkerGuard,
    rolling::{RollingFileAppender, Rotation},
};
use tracing_error::ErrorLayer;
use tracing_subscriber::{
    Layer, Registry,
    filter::{FilterExt, Targets, filter_fn},
    fmt::{MakeWriter, format::JsonFields, layer, time::LocalTime},
    layer::SubscriberExt,
    registry::LookupSpan,
    util::SubscriberInitExt,
};

struct LogEventWriter {
    app: AppHandle,
}

impl Write for LogEventWriter {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        let json_raw = String::from_utf8_lossy(buf).to_string();
        let _ = LogEvent { json_raw }.emit(&self.app);
        Ok(buf.len())
    }

    fn flush(&mut self) -> std::io::Result<()> {
        Ok(())
    }
}

struct LogEventWriterFactory {
    app: AppHandle,
}

impl MakeWriter<'_> for LogEventWriterFactory {
    type Writer = LogEventWriter;

    fn make_writer(&self) -> Self::Writer {
        LogEventWriter {
            app: self.app.clone(),
        }
    }
}

static RELOAD_FN: OnceLock<Box<dyn Fn() -> eyre::Result<()> + Send + Sync>> = OnceLock::new();
static GUARD: OnceLock<parking_lot::Mutex<Option<WorkerGuard>>> = OnceLock::new();

#[instrument(level = "error", skip_all)]
pub fn init(app: &AppHandle) -> eyre::Result<()> {
    let lib_module_path = module_path!();
    let lib_target = lib_module_path.split("::").next().ok_or_eyre(format!(
        "解析lib_target失败: lib_module_path={lib_module_path}"
    ))?;
    // 过滤掉来自其他库的日志
    let target_filter = Targets::new().with_target(lib_target, Level::TRACE);
    // 输出到文件
    let (file_layer, guard) = create_file_layer(app)?;
    let (reloadable_file_layer, reload_handle) = tracing_subscriber::reload::Layer::new(file_layer);
    // 输出到控制台
    let console_layer = layer()
        .with_writer(std::io::stdout)
        .with_timer(LocalTime::rfc_3339())
        .with_file(true)
        .with_line_number(true)
        .pretty();
    // 发送到前端
    let log_event_factory = LogEventWriterFactory { app: app.clone() };
    let log_event_layer = layer()
        .with_writer(log_event_factory)
        .with_timer(LocalTime::rfc_3339())
        .with_file(true)
        .with_line_number(true)
        .json()
        // 过滤掉来自这个文件的日志(LogEvent解析失败的日志)，避免无限递归
        .with_filter(target_filter.clone().and(filter_fn(|metadata| {
            metadata.module_path() != Some(lib_module_path)
        })));

    Registry::default()
        .with(target_filter)
        .with(reloadable_file_layer)
        .with(console_layer)
        .with(log_event_layer)
        .with(ErrorLayer::new(JsonFields::default()))
        .init();

    GUARD.get_or_init(|| parking_lot::Mutex::new(guard));
    RELOAD_FN.get_or_init(move || {
        let app = app.clone();
        Box::new(move || {
            let (file_layer, guard) = create_file_layer(&app)?;
            reload_handle.reload(file_layer).wrap_err("reload失败")?;
            *GUARD.get().ok_or_eyre("GUARD未初始化")?.lock() = guard;
            Ok(())
        })
    });
    tauri::async_runtime::spawn(file_log_watcher(app.clone()));

    Ok(())
}

#[instrument(level = "error", skip_all)]
pub fn reload_file_logger() -> eyre::Result<()> {
    RELOAD_FN.get().ok_or_eyre("RELOAD_FN未初始化")?()
}

#[instrument(level = "error", skip_all)]
pub fn disable_file_logger() -> eyre::Result<()> {
    if let Some(guard) = GUARD.get().ok_or_eyre("GUARD未初始化")?.lock().take() {
        drop(guard);
    }
    Ok(())
}

#[instrument(level = "error", skip_all)]
fn create_file_layer<S>(
    app: &AppHandle,
) -> eyre::Result<(Box<dyn Layer<S> + Send + Sync>, Option<WorkerGuard>)>
where
    S: Subscriber + for<'a> LookupSpan<'a>,
{
    let enable_file_logger = app.get_config().read().enable_file_logger;
    // 如果不启用文件日志，则返回一个占位用的sink layer，不创建也不输出日志文件
    if !enable_file_logger {
        let sink_layer = layer()
            .with_writer(std::io::sink)
            .with_timer(LocalTime::rfc_3339())
            .with_ansi(false)
            .with_file(true)
            .with_line_number(true)
            .json();
        return Ok((Box::new(sink_layer), None));
    }
    let logs_dir = logs_dir(app).wrap_err("获取日志目录失败")?;
    let file_appender = RollingFileAppender::builder()
        .filename_prefix("bilibili-video-downloader")
        .filename_suffix("log")
        .rotation(Rotation::DAILY)
        .build(&logs_dir)
        .wrap_err("创建RollingFileAppender失败")?;
    let (non_blocking_appender, guard) = tracing_appender::non_blocking(file_appender);
    let file_layer = layer()
        .with_writer(non_blocking_appender)
        .with_timer(LocalTime::rfc_3339())
        .with_ansi(false)
        .with_file(true)
        .with_line_number(true)
        .json();
    Ok((Box::new(file_layer), Some(guard)))
}

#[instrument(level = "error", skip_all)]
async fn file_log_watcher(app: AppHandle) {
    let (sender, mut receiver) = tokio::sync::mpsc::channel(1);
    let event_handler_span = tracing::error_span!("file_log_watcher_event_handler");

    let event_handler = move |res| {
        let send_event_task = async {
            if let Err(err) = sender.send(res).await.map_err(eyre::Report::from) {
                let err_title = "发送日志文件watcher事件失败";
                let message = err.to_message();
                tracing::error!(err_title, message);
            }
        };

        tauri::async_runtime::block_on(send_event_task.instrument(event_handler_span.clone()));
    };

    let mut watcher = match RecommendedWatcher::new(event_handler, notify::Config::default())
        .map_err(eyre::Report::from)
    {
        Ok(watcher) => watcher,
        Err(err) => {
            let err_title = "创建日志文件watcher失败";
            let message = err.to_message();
            tracing::error!(err_title, message);
            return;
        }
    };

    let logs_dir = match logs_dir(&app) {
        Ok(logs_dir) => logs_dir,
        Err(err) => {
            let err_title = "日志文件watcher获取日志目录失败";
            let message = err.to_message();
            tracing::error!(err_title, message);
            return;
        }
    };

    if let Err(err) = watcher
        .watch(&logs_dir, notify::RecursiveMode::NonRecursive)
        .map_err(eyre::Report::from)
    {
        let err_title = "日志文件watcher监听日志目录失败";
        let message = err.to_message();
        tracing::error!(err_title, message);
        return;
    }

    while let Some(res) = receiver.recv().await {
        match res.map_err(eyre::Report::from) {
            Ok(event) => {
                if let notify::EventKind::Remove(_) = event.kind
                    && let Err(err) = reload_file_logger()
                {
                    let err_title = "重置日志文件失败";
                    let message = err.to_message();
                    tracing::error!(err_title, message);
                }
            }
            Err(err) => {
                let err_title = "接收日志文件watcher事件失败";
                let message = err.to_message();
                tracing::error!(err_title, message);
            }
        }
    }
}

#[instrument(level = "error", skip_all)]
pub fn logs_dir(app: &AppHandle) -> eyre::Result<std::path::PathBuf> {
    let app_data_dir = app
        .path()
        .app_data_dir()
        .wrap_err("获取app_data_dir目录失败")?;
    Ok(app_data_dir.join("日志"))
}
