use std::{fs, io, os::fd::{AsRawFd, FromRawFd, IntoRawFd}};
use dirs;

use tracing::{Level, debug, info, level_filters::LevelFilter};
use tracing_subscriber::{EnvFilter, Layer, Registry, fmt::format::FmtSpan, layer::SubscriberExt};
use unicode_segmentation::GraphemeIncomplete;
use std::ffi::CStr;

pub fn setup_logging(args: &crate::Args) -> io::Result<()> {
    let default_level = if args.debug || cfg!(debug_assertions) { Level::DEBUG } else { Level::INFO };
    let env = EnvFilter::builder()
            .with_default_directive(default_level.into())
            .from_env().map_err(|_| io::Error::other("env filter failed"))?;

    let syslog_layer = {
        static IDENTITY: &'static CStr = c"atto";
        let (options, facility) = Default::default();
        syslog_tracing::Syslog::new(IDENTITY, options, facility).map(|writer| {
            tracing_subscriber::fmt::layer()
                .without_time()
                .with_target(false)
                .with_ansi(false)
                .with_writer(writer)
        })
    };

    let file_layer = {
        let file = dirs::cache_dir()
            .and_then(|cache_dir| fs::File::options()
                .write(true)
                .append(true)
                .create(true)
                .open(cache_dir.join("atto.log"))
                .ok()
            );
        file.map(|file| tracing_subscriber::fmt::layer()
            .with_line_number(true)
            .with_writer(file)
            .with_target(true)
            .with_ansi(true)
            .with_span_events(FmtSpan::ENTER | FmtSpan::CLOSE)
        )
    };

    let subscriber = Registry::default()
        .with(env)
        .with(syslog_layer)
        .with(file_layer);

    let _ = tracing::subscriber::set_global_default(subscriber);

    Ok(())
}

/// Trait for logging different kinds of errors
pub trait LogError {
    /// If this result is an error, log it as such
    fn log(self) -> Self;
}

impl<T> LogError for io::Result<T> {
    fn log(self) -> Self {
        if let Err(err) = &self {
            tracing::error!("{err:?}");
        }
        self
    }
}

impl<T> LogError for anyhow::Result<T> {
    fn log(self) -> Self {
        if let Err(err) = &self {
            tracing::error!("{err:?}");
        }
        self
    }
}

impl<T> LogError for core::result::Result<T, GraphemeIncomplete> {
    fn log(self) -> Self {
        if let Err(err) = &self {
            tracing::error!("{err:?}");
        }
        self
    }
}
