use std::collections::BTreeMap;

use serde::Serialize;
use serde_value::Value;

use super::{interpret::Interpret, topology::Topology};

pub trait Score<T: Topology>:
    std::fmt::Debug + ScoreToString<T> + Send + Sync + CloneBoxScore<T> + SerializeScore<T>
{
    fn create_interpret(&self) -> Box<dyn Interpret<T>>;
    fn name(&self) -> String;
}

pub trait SerializeScore<T: Topology> {
    fn serialize(&self) -> Value;
}

impl<'de, S, T> SerializeScore<T> for S
where
    T: Topology,
    S: Score<T> + Serialize,
{
    fn serialize(&self) -> Value {
        // TODO not sure if this is the right place to handle the error or it should bubble
        // up?
        serde_value::to_value(&self).expect("Score should serialize successfully")
    }
}

pub trait CloneBoxScore<T: Topology> {
    fn clone_box(&self) -> Box<dyn Score<T>>;
}

impl<S, T> CloneBoxScore<T> for S
where
    T: Topology,
    S: Score<T> + Clone + 'static,
{
    fn clone_box(&self) -> Box<dyn Score<T>> {
        Box::new(self.clone())
    }
}

pub trait ScoreToString<T: Topology> {
    fn print_score_details(&self) -> String;
    fn format_value_as_string(&self, val: &Value, indent: usize) -> String;
    fn format_map(&self, map: &BTreeMap<Value, Value>, indent: usize) -> String;
    fn wrap_or_truncate(&self, s: &str, width: usize) -> Vec<String>;
}

impl<S, T> ScoreToString<T> for S
where
    T: Topology,
    S: Score<T> + 'static,
{
    fn print_score_details(&self) -> String {
        let mut output = String::new();
        output += "\n";
        output += &self.format_value_as_string(&self.serialize(), 0);
        output += "\n";
        output
    }
    fn format_map(&self, map: &BTreeMap<Value, Value>, indent: usize) -> String {
        let pad = " ".repeat(indent * 2);
        let mut output = String::new();

        output += &format!(
            "{}+--------------------------+--------------------------------------------------+\n",
            pad
        );
        output += &format!("{}| {:<24} | {:<48} |\n", pad, "score_name", self.name());
        output += &format!(
            "{}+--------------------------+--------------------------------------------------+\n",
            pad
        );

        for (k, v) in map {
            let key_str = match k {
                Value::String(s) => s.clone(),
                other => format!("{:?}", other),
            };

            let formatted_val = self.format_value_as_string(v, indent + 1);
            let mut lines = formatted_val.lines().map(|line| line.trim_start());

            let wrapped_lines: Vec<_> = lines
                .flat_map(|line| self.wrap_or_truncate(line.trim_start(), 48))
                .collect();

            if let Some(first) = wrapped_lines.first() {
                output += &format!("{}| {:<24} | {:<48} |\n", pad, key_str, first);
                for line in &wrapped_lines[1..] {
                    output += &format!("{}| {:<24} | {:<48} |\n", pad, "", line);
                }
            }

            //            let first_line = lines.next().unwrap_or("");
            //            output += &format!("{}| {:<24} | {:<48} |\n", pad, key_str, first_line);
            //
            //            for line in lines {
            //                output += &format!("{}| {:<24} | {:<48} |\n", pad, "", line);
            //            }
        }

        output += &format!(
            "{}+--------------------------+--------------------------------------------------+\n\n",
            pad
        );

        output
    }

    fn wrap_or_truncate(&self, s: &str, width: usize) -> Vec<String> {
        let mut lines = Vec::new();
        let mut current = s;

        while !current.is_empty() {
            if current.len() <= width {
                lines.push(current.to_string());
                break;
            }

            // Try to wrap at whitespace if possible
            let mut split_index = current[..width].rfind(' ').unwrap_or(width);
            if split_index == 0 {
                split_index = width;
            }

            lines.push(current[..split_index].trim_end().to_string());
            current = current[split_index..].trim_start();
        }

        lines
    }

    fn format_value_as_string(&self, val: &Value, indent: usize) -> String {
        let pad = " ".repeat(indent * 2);
        let mut output = String::new();

        match val {
            Value::Bool(b) => output += &format!("{}{}\n", pad, b),
            Value::U8(u) => output += &format!("{}{}\n", pad, u),
            Value::U16(u) => output += &format!("{}{}\n", pad, u),
            Value::U32(u) => output += &format!("{}{}\n", pad, u),
            Value::U64(u) => output += &format!("{}{}\n", pad, u),
            Value::I8(i) => output += &format!("{}{}\n", pad, i),
            Value::I16(i) => output += &format!("{}{}\n", pad, i),
            Value::I32(i) => output += &format!("{}{}\n", pad, i),
            Value::I64(i) => output += &format!("{}{}\n", pad, i),
            Value::F32(f) => output += &format!("{}{}\n", pad, f),
            Value::F64(f) => output += &format!("{}{}\n", pad, f),
            Value::Char(c) => output += &format!("{}{}\n", pad, c),
            Value::String(s) => output += &format!("{}{:<48}\n", pad, s),
            Value::Unit => output += &format!("{}<unit>\n", pad),
            Value::Bytes(bytes) => output += &format!("{}{:?}\n", pad, bytes),

            Value::Option(opt) => match opt {
                Some(inner) => {
                    output += &format!("{}Option:\n", pad);
                    output += &self.format_value_as_string(inner, indent + 1);
                }
                None => output += &format!("{}None\n", pad),
            },

            Value::Newtype(inner) => {
                output += &format!("{}Newtype:\n", pad);
                output += &self.format_value_as_string(inner, indent + 1);
            }

            Value::Seq(seq) => {
                if seq.is_empty() {
                    output += &format!("{}[]\n", pad);
                } else {
                    output += &format!("{}[\n", pad);
                    for item in seq {
                        output += &self.format_value_as_string(item, indent + 1);
                    }
                    output += &format!("{}]\n", pad);
                }
            }

            Value::Map(map) => {
                if map.is_empty() {
                    output += &format!("{}<empty map>\n", pad);
                } else if indent == 0 {
                    output += &self.format_map(map, indent);
                } else {
                    for (k, v) in map {
                        let key_str = match k {
                            Value::String(s) => s.clone(),
                            other => format!("{:?}", other),
                        };

                        let val_str = self
                            .format_value_as_string(v, indent + 1)
                            .trim()
                            .to_string();
                        let val_lines: Vec<_> = val_str.lines().collect();

                        output +=
                            &format!("{}{}: {}\n", pad, key_str, val_lines.first().unwrap_or(&""));
                        for line in val_lines.iter().skip(1) {
                            output += &format!("{}  {}\n", pad, line);
                        }
                    }
                }
            }
        }

        output
    }
}

//TODO write test to check that the output is what it should be
//
#[cfg(test)]
mod tests {
    use super::*;
    use crate::modules::dns::DnsScore;
    use crate::topology::{self, HAClusterTopology};

    #[test]
    fn test_format_values_as_string() {
        let dns_score = Box::new(DnsScore::new(vec![], None));
        let print_score_output =
            <DnsScore as ScoreToString<HAClusterTopology>>::print_score_details(&dns_score);
        let expected_empty_dns_score_table = "\n+--------------------------+--------------------------------------------------+\n| score_name               | DnsScore                                         |\n+--------------------------+--------------------------------------------------+\n| dns_entries              | []                                               |\n| register_dhcp_leases     | None                                             |\n+--------------------------+--------------------------------------------------+\n\n\n";
        assert_eq!(print_score_output, expected_empty_dns_score_table);
    }
}
