use std::path::PathBuf;
use std::sync::OnceLock;
use tokio::sync::broadcast;
use tracing::field::{Field, Visit};
use tracing::{Event, Subscriber};
use tracing_appender::rolling::{RollingFileAppender, Rotation};
use tracing_subscriber::Layer;
use tracing_subscriber::layer::Context;
use tracing_subscriber::registry::LookupSpan;
use tracing_subscriber::{
    EnvFilter, fmt, fmt::time::LocalTime, layer::SubscriberExt, util::SubscriberInitExt,
};

#[derive(Clone, Debug)]
pub struct LogEvent {
    pub ts: String,
    pub level: String,
    pub target: String,
    pub message: String,
}

static LOG_TX: OnceLock<broadcast::Sender<LogEvent>> = OnceLock::new();

pub fn log_sender() -> Option<broadcast::Sender<LogEvent>> {
    LOG_TX.get().cloned()
}

struct MessageVisitor(String);

impl Visit for MessageVisitor {
    fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
        if field.name() == "message" {
            self.0 = format!("{value:?}");
        }
    }

    fn record_str(&mut self, field: &Field, value: &str) {
        if field.name() == "message" {
            self.0 = value.to_string();
        }
    }
}

struct BroadcastLayer;

impl<S> Layer<S> for BroadcastLayer
where
    S: Subscriber + for<'a> LookupSpan<'a>,
{
    fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
        let Some(tx) = LOG_TX.get() else {
            return;
        };
        if tx.receiver_count() == 0 {
            return;
        }

        let mut visitor = MessageVisitor(String::new());
        event.record(&mut visitor);

        let ts = time::OffsetDateTime::now_local()
            .unwrap_or_else(|_| time::OffsetDateTime::now_utc())
            .format(&time::format_description::well_known::Rfc3339)
            .unwrap_or_default();

        let _ = tx.send(LogEvent {
            ts,
            level: event.metadata().level().to_string(),
            target: event.metadata().target().to_string(),
            message: visitor.0,
        });
    }
}

pub fn get_default_log_dir() -> PathBuf {
    let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
    home.join(".picobot").join("logs")
}

pub fn get_default_config_path() -> PathBuf {
    let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
    home.join(".picobot").join("config.json")
}

pub fn init_logging() {
    let (tx, _) = broadcast::channel::<LogEvent>(1024);
    let _ = LOG_TX.set(tx);

    let log_dir = get_default_log_dir();

    if !log_dir.exists()
        && let Err(e) = std::fs::create_dir_all(&log_dir)
    {
        eprintln!(
            "Warning: Failed to create log directory {}: {}",
            log_dir.display(),
            e
        );
    }

    let file_appender = RollingFileAppender::new(Rotation::DAILY, &log_dir, "picobot.log");

    let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));

    let file_layer = fmt::layer()
        .with_writer(file_appender)
        .with_timer(LocalTime::rfc_3339())
        .with_ansi(false)
        .with_target(true)
        .with_level(true)
        .with_thread_ids(true);

    let console_layer = fmt::layer()
        .with_timer(LocalTime::rfc_3339())
        .with_target(true)
        .with_level(true);

    tracing_subscriber::registry()
        .with(env_filter)
        .with(console_layer)
        .with(file_layer)
        .with(BroadcastLayer)
        .init();

    tracing::info!("Logging initialized. Log directory: {}", log_dir.display());
}
