use std::fmt;

use tracing::field::Field;
use tracing::{Event, Level, Metadata, Subscriber};
use tracing::span::{Attributes, Id, Record};
use tracing_subscriber::layer::{Layer, Context};
use tracing_subscriber::registry::LookupSpan;
use tracing_subscriber::field::RecordFields;

use yansi::{Paint, Painted};

use crate::util::Formatter;
use crate::trace::subscriber::{Data, RecordDisplay, RocketFmt};

#[derive(Debug, Default, Copy, Clone)]
pub struct Pretty {
    depth: u32,
}

impl RocketFmt<Pretty> {
    fn indent(&self) -> &'static str {
        static INDENT: &[&str] = &["", "   ", "      "];
        INDENT.get(self.state().depth as usize).copied().unwrap_or("         ")
    }

    fn marker(&self) -> &'static str {
        static MARKER: &[&str] = &["", ">> ", ":: "];
        MARKER.get(self.state().depth as usize).copied().unwrap_or("-- ")
    }

    fn emoji(&self, _emoji: &'static str) -> Painted<&'static str> {
        #[cfg(windows)] { "".paint(self.style).mask() }
        #[cfg(not(windows))] { _emoji.paint(self.style).mask() }
    }

    fn prefix<'a>(&self, meta: &'a Metadata<'_>) -> impl fmt::Display + 'a {
        let (i, m, s) = (self.indent(), self.marker(), self.style(meta));
        Formatter(move |f| match *meta.level() {
            Level::WARN => write!(f, "{i}{m}{} ", "warning:".paint(s).bold()),
            Level::ERROR => write!(f, "{i}{m}{} ", "error:".paint(s).bold()),
            Level::INFO => write!(f, "{i}{m}"),
            level => write!(f, "{i}{m}[{} {}] ", level.paint(s).bold(), meta.target()),
        })
    }

    fn print_pretty<F: RecordFields>(&self, m: &Metadata<'_>, data: F) {
        let prefix = self.prefix(m);
        let cont_prefix = Formatter(|f| {
            let style = self.style(m);
            write!(f, "{}{} ", self.indent(), "++".paint(style).dim())
        });

        self.print(&prefix, &cont_prefix, m, data);
    }

    fn print_fields<F>(&self, metadata: &Metadata<'_>, fields: F)
        where F: RecordFields
    {
        let style = self.style(metadata);
        let prefix = self.prefix(metadata);
        fields.record_display(|key: &Field, value: &dyn fmt::Display| {
            if key.name() != "message" {
                println!("{prefix}{}: {}", key.paint(style), value.paint(style).primary());
            }
        })
    }
}

impl<S: Subscriber + for<'a> LookupSpan<'a>> Layer<S> for RocketFmt<Pretty> {
    fn enabled(&self, metadata: &Metadata<'_>, _: Context<'_, S>) -> bool {
        self.filter.would_enable(metadata.target(), metadata.level())
    }

    fn on_event(&self, event: &Event<'_>, _: Context<'_, S>) {
        let (meta, data) = (event.metadata(), Data::new(event));
        let style = self.style(meta);
        match meta.name() {
            "config" => self.print_fields(meta, event),
            "liftoff" => {
                let prefix = self.prefix(meta);
                println!("{prefix}{}{} {}", self.emoji("🚀 "),
                    "Rocket has launched on".paint(style).primary().bold(),
                    &data["endpoint"].paint(style).primary().bold().underline());
            },
            "route" => println!("{}", Formatter(|f| {
                write!(f, "{}{}{}: ", self.indent(), self.marker(), "route".paint(style))?;

                let (base, mut relative) = (&data["uri.base"], &data["uri.unmounted"]);
                if base.ends_with('/') && relative.starts_with('/') {
                    relative = &relative[1..];
                }

                write!(f, "{:>3} {} {}{}",
                    &data["rank"].paint(style.bright().dim()),
                    &data["method"].paint(style.bold()),
                    base.paint(style.primary().underline()),
                    relative.paint(style.primary()),
                )?;

                if let Some(name) = data.get("name") {
                    write!(f, " ({}", name.paint(style.bold().bright()))?;

                    if let Some(location) = data.get("location") {
                        write!(f, " {}", location.paint(style.dim()))?;
                    }

                    write!(f, ")")?;
                }

                Ok(())
            })),
            "catcher" => println!("{}", Formatter(|f| {
                write!(f, "{}{}{}: ", self.indent(), self.marker(), "catcher".paint(style))?;

                match data.get("code") {
                    Some(code) => write!(f, "{} ", code.paint(style.bold()))?,
                    None => write!(f, "{} ", "default".paint(style.bold()))?,
                }

                write!(f, "{}", &data["uri.base"].paint(style.primary()))?;
                if let Some(name) = data.get("name") {
                    write!(f, " ({}", name.paint(style.bold().bright()))?;

                    if let Some(location) = data.get("location") {
                        write!(f, " {}", location.paint(style.dim()))?;
                    }

                    write!(f, ")")?;
                }

                Ok(())
            })),
            "header" => println!("{}{}{}: {}: {}",
                self.indent(), self.marker(), "header".paint(style),
                &data["name"].paint(style.bold()),
                &data["value"].paint(style.primary()),
            ),
            "fairing" => println!("{}{}{}: {} {}",
                self.indent(), self.marker(), "fairing".paint(style),
                &data["name"].paint(style.bold()),
                &data["kind"].paint(style.primary().dim()),
            ),
            _ => self.print_pretty(meta, event),
        }
    }

    fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctxt: Context<'_, S>) {
        let data = Data::new(attrs);
        let span = ctxt.span(id).expect("new_span: span does not exist");
        if &data["count"] != "0" {
            let name = span.name();
            let icon = match name {
                "config" => "🔧 ",
                "routes" => "📬 ",
                "catchers" => "🚧 ",
                "fairings" => "📦 ",
                "shield" => "🛡️ ",
                "templating" => "📐 ",
                "request" => "● ",
                _ => "",
            };

            let meta = span.metadata();
            let style = self.style(meta);
            let emoji = self.emoji(icon);
            let name = name.paint(style).bold();

            let fields = self.compact_fields(meta, attrs);
            let prefix = self.prefix(meta);
            let fieldless_prefix = Formatter(|f| write!(f, "{prefix}{emoji}{name} "));
            let field_prefix = Formatter(|f| write!(f, "{prefix}{emoji}{name} ({fields}) "));

            if self.has_message(meta) && self.has_data_fields(meta) {
                print!("{}", self.message(&field_prefix, &fieldless_prefix, meta, attrs));
            } else if self.has_message(meta) {
                print!("{}", self.message(&fieldless_prefix, &fieldless_prefix, meta, attrs));
            } else if self.has_data_fields(meta) {
                println!("{field_prefix}");
            } else {
                println!("{fieldless_prefix}");
            }
        }

        span.extensions_mut().replace(data);
    }

    fn on_record(&self, id: &Id, values: &Record<'_>, ctxt: Context<'_, S>) {
        let span = ctxt.span(id).expect("new_span: span does not exist");
        match span.extensions_mut().get_mut::<Data>() {
            Some(data) => values.record(data),
            None => span.extensions_mut().insert(Data::new(values)),
        }

        let meta = span.metadata();
        println!("{}{}", self.prefix(meta), self.compact_fields(meta, values));
    }

    fn on_enter(&self, _: &Id, _: Context<'_, S>) {
        self.update_state(|state| state.depth = state.depth.saturating_add(1));
    }

    fn on_exit(&self, _: &Id, _: Context<'_, S>) {
        self.update_state(|state| state.depth = state.depth.saturating_sub(1));
    }
}
