// Copyright Two Neutron Stars Incorporated and contributors
// SPDX-License-Identifier: BlueOak-1.0.0

use {
  super::Bluegum,
  crate::{
    Nodes,
    Printer,
  },
  std::ops::Deref,
};

enum Tree {
  // Node has fields and descendent nodes
  Node {
    name:        String,
    value:       String,
    nodes:       Vec<Tree>,
    other_nodes: Vec<OtherTree>,
  },
  // Fragment has nodes but no fields
  Fragment {
    nodes:       Vec<Tree>,
    other_nodes: Vec<OtherTree>,
  },
  // Leaf has fields but no nodes
  Leaf {
    name:  String,
    value: String,
  },
}

impl Bluegum for Tree {
  fn node(&self, b: &mut crate::Builder) {
    match self {
      | Tree::Node {
        name,
        value,
        nodes,
        other_nodes,
      } => {
        b.name("Tree::Node")
          .field("name", name)
          .field("value", value)
          .add_nodes("nodes", nodes)
          .add_nodes("other_nodes", other_nodes)
      },
      | Tree::Fragment { nodes, other_nodes } => {
        b.name("Tree::Fragment")
          .field("nodes", "nodes")
          .add_nodes("nodes", nodes)
          .add_nodes("other_nodes", other_nodes)
      },
      | Tree::Leaf { name, value } => {
        b.name("Tree::Leaf")
          .field("name", name)
          .field("value", value)
      },
    };
  }
}

enum OtherTree {
  // Node has fields and nodes
  Node {
    other_name:  String,
    other_value: String,
    nodes:       Vec<Tree>,
  },
  // Fragment has nodes but no fields
  Fragment {
    nodes: Vec<Tree>,
  },
  // Leaf has fields but no nodes
  Leaf {
    other_name:  String,
    other_value: String,
  },
}

impl Bluegum for OtherTree {
  fn node(&self, b: &mut crate::Builder) {
    match self {
      | OtherTree::Node {
        other_name,
        other_value,
        nodes,
      } => {
        b.name("OtherTree::OtherNode")
          .field("other_name", other_name)
          .field("other_value", other_value)
          .add_nodes("nodes", nodes)
      },
      | OtherTree::Fragment { nodes } => {
        b.name("OtherTree::OtherFragment").add_nodes("nodes", nodes)
      },
      | OtherTree::Leaf {
        other_name,
        other_value,
      } => {
        b.name("OtherTree::OtherLeaf")
          .field("other_name", other_name)
          .field("other_value", other_value)
      },
    };
  }
}

fn basic_tree() -> Tree {
  Tree::Node {
    name:        "r0".to_string(),
    value:       "_".to_string(),
    nodes:       vec![Tree::Node {
      name:        "r0.c0".to_string(),
      value:       "_".to_string(),
      nodes:       vec![Tree::Node {
        name:        "r0.c0.c0".to_string(),
        value:       "_".to_string(),
        nodes:       vec![Tree::Leaf {
          name:  "r0.c0.c0.c0".to_string(),
          value: "_".to_string(),
        }],
        other_nodes: vec![OtherTree::Leaf {
          other_name:  "r0.c0.c0.o0".to_string(),
          other_value: "_".to_string(),
        }],
      }],
      other_nodes: vec![OtherTree::Leaf {
        other_name:  "r0.c0.o0".to_string(),
        other_value: "_".to_string(),
      }],
    }],
    other_nodes: vec![
      OtherTree::Leaf {
        other_name:  "r0.o0".to_string(),
        other_value: "_".to_string(),
      },
      OtherTree::Leaf {
        other_name:  "r0.o1".to_string(),
        other_value: "_".to_string(),
      },
      OtherTree::Node {
        other_name:  "r0.02".to_string(),
        other_value: "_".to_string(),
        nodes:       vec![Tree::Node {
          name:        "r0.02.c0".to_string(),
          value:       "_".to_string(),
          nodes:       vec![
            Tree::Leaf {
              name:  "r0.02.c0.c0".to_string(),
              value: "_".to_string(),
            },
            Tree::Leaf {
              name:  "r0.02.c0.c1".to_string(),
              value: "_".to_string(),
            },
            Tree::Leaf {
              name:  "r0.02.c0.c2".to_string(),
              value: "_".to_string(),
            },
          ],
          other_nodes: vec![
            OtherTree::Node {
              other_name:  "r0.02.o0".to_string(),
              other_value: "has no nodes".to_string(),
              nodes:       vec![],
            },
            OtherTree::Leaf {
              other_name:  "r0.02.o1".to_string(),
              other_value: "_".to_string(),
            },
            OtherTree::Leaf {
              other_name:  "r0.02.o2".to_string(),
              other_value: "_".to_string(),
            },
          ],
        }],
      },
      OtherTree::Leaf {
        other_name:  "r0.o3".to_string(),
        other_value: "_".to_string(),
      },
    ],
  }
}

#[test]
fn basic_bluegum() {
  let tree = basic_tree();

  let mut p = Printer::default();

  p.render(&tree);

  insta::with_settings!(
    {snapshot_path => std::path::PathBuf::from(".snapshots")},
    {insta::assert_snapshot!(p.strip_color())}
  );
}

#[test]
fn basic_bluegum_verbose() {
  let tree = basic_tree();

  let mut p = Printer::default();
  p.render(&tree);

  p.to_stdout();

  insta::with_settings!(
    {snapshot_path => std::path::PathBuf::from(".snapshots")},
    { insta::assert_snapshot!(p.strip_color()) });
}

enum Items<'src> {
  ImportStatement(ImportStatement),
  Expression(Expression<'src>),
  Block(Vec<Items<'src>>),
}

impl<'src> Bluegum for Items<'src> {
  fn node(&self, b: &mut crate::Builder) {
    match self {
      | Items::ImportStatement(imports) => {
        b.name("Item::ImportStatement")
          .add_nodes("items", &imports.items)
      },
      | Items::Expression(expr) => {
        b.name("Item::Expression").add_node("expr", expr)
      },
      | Items::Block(block) => b.name("Item::Block").add_nodes("block", block),
    };
  }
}

struct ImportStatement {
  items: Vec<String>,
}

impl Bluegum for ImportStatement {
  fn node(&self, b: &mut crate::Builder) {
    b.name("ImportStatement").add_nodes("items", &self.items);
  }
}

enum Expression<'src> {
  Literal(&'src str),
  Call(&'src str, Vec<Expression<'src>>),
  Binary(Box<Expression<'src>>, Box<Expression<'src>>),
}

impl Bluegum for Expression<'_> {
  fn node(&self, b: &mut crate::Builder) {
    match self {
      | Expression::Literal(val) => {
        b.name("Expression::Literal").field("val", val);
      },
      | Expression::Call(ident, block) => {
        b.name("Expression::Call")
          .field("ident", ident)
          .add_nodes("block", block);
      },
      | Expression::Binary(left, right) => {
        b.name("Expression::Binary")
          .add_nodes("left", left)
          .add_nodes("right", right);
      },
    }
  }
}

#[test]
fn test_ast() {
  let tree = Items::Block(vec![
    Items::ImportStatement(ImportStatement {
      items: vec!["foo".to_string(), "bar".to_string()],
    }),
    Items::Expression(Expression::Call("foo", vec![
      Expression::Literal("1"),
      Expression::Literal("2"),
      Expression::Binary(
        Box::new(Expression::Literal("3")),
        Box::new(Expression::Literal("4")),
      ),
    ])),
  ]);

  let mut p = Printer::default();

  p.render(&tree);

  p.to_stdout();

  insta::with_settings!(
    {snapshot_path => std::path::PathBuf::from(".snapshots")},
    { insta::assert_snapshot!(p.strip_color()) });
}

// Test all Builder methods
#[test]
fn test_builder_methods() {
  use crate::Builder;

  struct TestNode;
  impl crate::Bluegum for TestNode {
    fn node(&self, b: &mut crate::Builder) {
      b.name("TestNode")
        .field("name", "example")
        .field("count", 42)
        .option_field("optional_present", Some("value"))
        .option_field("optional_missing", None::<String>)
        .alt("alternate_info")
        .debug("debug_key", "debug_value")
        .option_debug("debug_optional", Some("debug_present"))
        .option_debug("debug_missing", None::<String>)
        .divider()
        .field("after_divider", "test");
    }
  }

  let node = TestNode;
  let mut p = Printer::default();
  p.render(&node);

  let output = p.strip_color();
  assert!(output.contains("TestNode"));
  assert!(output.contains("name:") && output.contains("example"));
  assert!(output.contains("count:") && output.contains("42"));
  assert!(output.contains("optional_present:") && output.contains("value"));
  assert!(!output.contains("optional_missing"));
  assert!(output.contains("alternate_info"));
  assert!(output.contains("after_divider:") && output.contains("test"));
}

// Test primitive types
#[test]
fn test_primitive_types() {
  let primitives = vec![
    crate::Builder::render(&42u32),
    crate::Builder::render(&"hello".to_string()),
    crate::Builder::render(&true),
    crate::Builder::render(&std::f64::consts::PI),
    crate::Builder::render(&'c'),
  ];

  for builder in primitives {
    let mut p = Printer::default();
    p.render_builder(builder);
    let output = p.strip_color();
    // Each primitive should have its type name and debug representation
    assert!(!output.is_empty());
  }
}

// Test Vec and tuple implementations
#[test]
fn test_collections() {
  let vec_node = vec!["a".to_string(), "b".to_string(), "c".to_string()];
  let tuple_node = ("first".to_string(), 42u32);

  let mut p = Printer::default();
  p.render(&vec_node);
  let vec_output = p.strip_color();
  assert!(vec_output.contains("Vec"));
  assert!(vec_output.contains("items"));

  let mut p = Printer::default();
  p.render(&tuple_node);
  let tuple_output = p.strip_color();
  assert!(tuple_output.contains("(T1,T2)"));
  assert!(tuple_output.contains("T1"));
  assert!(tuple_output.contains("T2"));
}

// Test empty collections
#[test]
fn test_empty_collections() {
  let empty_vec: Vec<String> = vec![];

  let mut p = Printer::default();
  p.render(&empty_vec);
  let output = p.strip_color();
  assert!(output.contains("Vec"));
  assert!(output.contains("items"));
}

// Test Leaf helper
#[test]
fn test_leaf_helper() {
  let leaf = crate::Leaf::new("MyLeaf".to_string(), &[
    ("field1", "value1".to_string()),
    ("field2", "value2".to_string()),
  ]);

  let mut p = Printer::default();
  p.render(&leaf);
  let output = p.strip_color();
  assert!(output.contains("MyLeaf"));
  assert!(output.contains("field1: value1"));
  assert!(output.contains("field2: value2"));
}

// Test different printer styles
#[test]
fn test_printer_styles() {
  struct SimpleNode;
  impl crate::Bluegum for SimpleNode {
    fn node(&self, b: &mut crate::Builder) {
      b.name("SimpleNode").field("test", "value");
    }
  }

  let node = SimpleNode;

  // Test with line numbers hidden
  let mut styles = crate::Styles {
    hide_line_numbers: true,
    ..Default::default()
  };
  let mut p = Printer::new(styles);
  p.render(&node);
  let output_no_lines = p.strip_color();
  assert!(!output_no_lines.starts_with("00│"));

  // Test with lines hidden
  let mut styles = crate::Styles {
    hide_lines: true,
    ..Default::default()
  };
  let mut p = Printer::new(styles);
  p.render(&node);
  let output_no_tree_lines = p.strip_color();
  // Should still have content but different formatting
  assert!(output_no_tree_lines.contains("SimpleNode"));
}

// Test printer title
#[test]
fn test_printer_title() {
  struct SimpleNode;
  impl crate::Bluegum for SimpleNode {
    fn node(&self, b: &mut crate::Builder) {
      b.name("SimpleNode");
    }
  }

  let node = SimpleNode;
  let mut p = Printer::default().set_title("My Tree");
  p.render(&node);
  let output = p.strip_color();
  assert!(output.contains("My Tree"));
}

// Test complex nested structure
#[test]
fn test_complex_nested_structure() {
  struct ComplexNode {
    name:     String,
    children: Vec<ComplexNode>,
    metadata: Option<String>,
  }

  impl crate::Bluegum for ComplexNode {
    fn node(&self, b: &mut crate::Builder) {
      b.name("ComplexNode")
        .field("name", &self.name)
        .option_field("metadata", self.metadata.as_ref())
        .add_nodes("children", &self.children);
    }
  }

  let root = ComplexNode {
    name:     "root".to_string(),
    metadata: Some("root_meta".to_string()),
    children: vec![
      ComplexNode {
        name:     "child1".to_string(),
        metadata: None,
        children: vec![ComplexNode {
          name:     "grandchild".to_string(),
          metadata: Some("grandchild_meta".to_string()),
          children: vec![],
        }],
      },
      ComplexNode {
        name:     "child2".to_string(),
        metadata: Some("child2_meta".to_string()),
        children: vec![],
      },
    ],
  };

  let mut p = Printer::default();
  p.render(&root);
  let output = p.strip_color();

  assert!(output.contains("root"));
  assert!(output.contains("child1"));
  assert!(output.contains("child2"));
  assert!(output.contains("grandchild"));
  assert!(output.contains("root_meta"));
  assert!(output.contains("child2_meta"));
  assert!(output.contains("grandchild_meta"));
  assert!(!output.contains("None"));
}

// Test BluegumWithState functionality
#[test]
fn test_bluegum_with_state() {
  struct SymbolTable {
    symbols: Vec<String>,
  }

  struct Variable {
    symbol_id: usize,
    value:     i32,
  }

  impl crate::Bluegum for Variable {
    fn node(&self, b: &mut crate::Builder) {
      b.name("Variable")
        .field("symbol_id", self.symbol_id)
        .field("value", self.value);
    }
  }

  impl crate::BluegumWithState<SymbolTable> for Variable {
    fn node_with_state(&self, b: &mut crate::Builder, symbols: &SymbolTable) {
      b.name("Variable")
        .field("name", &symbols.symbols[self.symbol_id])
        .alt(format!("id:{}", self.symbol_id))
        .field("value", self.value);
    }
  }

  let symbols = SymbolTable {
    symbols: vec!["x".to_string(), "y".to_string(), "result".to_string()],
  };

  let var = Variable {
    symbol_id: 0,
    value:     42,
  };

  // Test without state
  let mut p = Printer::default();
  p.render(&var);
  let output_no_state = p.strip_color();
  assert!(output_no_state.contains("symbol_id: 0"));
  assert!(!output_no_state.contains("name: x"));

  // Test with state
  let mut p = Printer::default();
  let builder_with_state = crate::Builder::render_with_state(&var, &symbols);
  p.render_builder(builder_with_state);
  let output_with_state = p.strip_color();
  assert!(
    output_with_state.contains("name:") && output_with_state.contains("x")
  );
  assert!(output_with_state.contains("id:0"));
  assert!(
    output_with_state.contains("value:") && output_with_state.contains("42")
  );
}

// Test BluegumWithState for collections
#[test]
fn test_bluegum_with_state_collections() {
  struct Context {
    prefix: String,
  }

  struct Item {
    name: String,
  }

  impl crate::Bluegum for Item {
    fn node(&self, b: &mut crate::Builder) {
      b.name("Item").field("name", &self.name);
    }
  }

  impl crate::BluegumWithState<Context> for Item {
    fn node_with_state(&self, b: &mut crate::Builder, ctx: &Context) {
      b.name("Item")
        .field("full_name", format!("{}{}", ctx.prefix, self.name));
    }
  }

  struct Container {
    items: Vec<Item>,
  }

  impl crate::Bluegum for Container {
    fn node(&self, b: &mut crate::Builder) {
      b.name("Container").add_nodes("items", &self.items);
    }
  }

  impl crate::BluegumWithState<Context> for Container {
    fn node_with_state(&self, b: &mut crate::Builder, ctx: &Context) {
      b.name("Container").add_nodes_with_state::<Context>(
        ctx,
        "items",
        &self.items,
      );
    }
  }

  let container = Container {
    items: vec![
      Item {
        name: "first".to_string(),
      },
      Item {
        name: "second".to_string(),
      },
    ],
  };

  let context = Context {
    prefix: "test_".to_string(),
  };

  let mut p = Printer::default();
  let builder_with_state =
    crate::Builder::render_with_state(&container, &context);
  p.render_builder(builder_with_state);
  let output = p.strip_color();

  assert!(output.contains("Container"));
  assert!(output.contains("test_first"));
  assert!(output.contains("test_second"));
}

// Test convenience functions
#[test]
fn test_convenience_functions() {
  struct TestNode;
  impl crate::Bluegum for TestNode {
    fn node(&self, b: &mut crate::Builder) {
      b.name("TestNode");
    }
  }

  let node = TestNode;

  // Test render function
  let output = crate::render(&node);
  assert!(output.contains("TestNode"));
  assert!(!output.contains('\x1b')); // Should not contain ANSI codes
}

// Test different node types (Node, Fragment, Leaf)
#[test]
fn test_node_types() {
  enum NodeType {
    Node {
      name:     String,
      children: Vec<NodeType>,
    },
    Fragment {
      children: Vec<NodeType>,
    },
    Leaf {
      value: String,
    },
  }

  impl crate::Bluegum for NodeType {
    fn node(&self, b: &mut crate::Builder) {
      match self {
        | NodeType::Node { name, children } => {
          b.name("Node")
            .field("name", name)
            .add_nodes("children", children);
        },
        | NodeType::Fragment { children } => {
          b.name("Fragment").add_nodes("children", children);
        },
        | NodeType::Leaf { value } => {
          b.name("Leaf").field("value", value);
        },
      }
    }
  }

  let tree = NodeType::Node {
    name:     "root".to_string(),
    children: vec![
      NodeType::Fragment {
        children: vec![
          NodeType::Leaf {
            value: "leaf1".to_string(),
          },
          NodeType::Leaf {
            value: "leaf2".to_string(),
          },
        ],
      },
      NodeType::Leaf {
        value: "leaf3".to_string(),
      },
    ],
  };

  let mut p = Printer::default();
  p.render(&tree);
  let output = p.strip_color();

  assert!(output.contains("Node"));
  assert!(output.contains("Fragment"));
  assert!(output.contains("Leaf"));
  assert!(output.contains("leaf1"));
  assert!(output.contains("leaf2"));
  assert!(output.contains("leaf3"));
}

// Test builder with mixed node types
#[test]
fn test_mixed_builder_usage() {
  struct MixedNode;
  impl crate::Bluegum for MixedNode {
    fn node(&self, b: &mut crate::Builder) {
      let child1 = crate::Builder::render(&"child1".to_string());
      let child2 = crate::Builder::render(&42);

      b.name("MixedNode")
        .field("type", "mixed")
        .add_named_builder("string_child", child1)
        .add_named_builder("number_child", child2)
        .add_builders(vec![
          crate::Builder::render(&true),
          crate::Builder::render(&std::f32::consts::PI),
        ]);
    }
  }

  let node = MixedNode;
  let mut p = Printer::default();
  p.render(&node);
  let output = p.strip_color();

  assert!(output.contains("MixedNode"));
  assert!(output.contains("type: mixed"));
  assert!(output.contains("string_child"));
  assert!(output.contains("number_child"));
  assert!(output.contains("child1"));
  assert!(output.contains("42"));
  assert!(output.contains("true"));
  assert!(output.contains("3.14"));
}

#[cfg(feature = "url")]
#[test]
fn test_url_support() {
  use url::Url;

  let url =
    Url::parse("https://user:pass@example.com:8080/path?query=value#fragment")
      .unwrap();

  let mut p = Printer::default();
  p.render(&url);
  let output = p.strip_color();

  dbg!(&output);

  assert!(output.contains("Url"));
  assert!(output.contains("scheme:   https"));
  assert!(output.contains("username: user"));
  assert!(output.contains("password: pass"));
  assert!(output.contains("host:     example.com"));
  assert!(output.contains("port:     8080"));
  assert!(output.contains("path:     /path"));
  assert!(output.contains("query:    query=value"));
  assert!(output.contains("fragment: fragment"));
}

#[test]
fn test_edge_cases() {
  struct EdgeCaseNode;
  impl crate::Bluegum for EdgeCaseNode {
    fn node(&self, b: &mut crate::Builder) {
      b.name("")  // Empty name
        .field("", "empty_key")  // Empty field name
        .field("key", "")  // Empty field value
        .alt("")  // Empty alt
        .debug("", ""); // Empty debug
    }
  }

  let node = EdgeCaseNode;
  let mut p = Printer::default();
  p.render(&node);
  let output = p.strip_color();

  // Should handle empty strings gracefully
  assert!(!output.is_empty());
}

// Test with very long content
#[test]
fn test_long_content() {
  struct LongContentNode;
  impl crate::Bluegum for LongContentNode {
    fn node(&self, b: &mut crate::Builder) {
      let long_string = "a".repeat(1000);
      b.name("LongContentNode")
        .field("long_field", &long_string)
        .alt(&long_string)
        .debug("long_debug", &long_string);
    }
  }

  let node = LongContentNode;
  let mut p = Printer::default();
  p.render(&node);
  let output = p.strip_color();

  assert!(output.contains("LongContentNode"));
  assert!(!output.is_empty());
  assert!(output.len() > 1000); // Should contain the long content
}

// Test the three key features: alt values, debug info, and flexible structure
#[test]
fn test_advanced_features() {
  // Demonstrate a data structure that doesn't naturally form a tree
  struct Database {
    tables:        Vec<String>,
    relationships: Vec<(usize, usize, String)>, /* (from_table, to_table,
                                                 * relation_type) */
    metadata:      std::collections::HashMap<usize, String>,
  }

  impl crate::Bluegum for Database {
    fn node(&self, b: &mut crate::Builder) {
      b.name("Database")
        .debug("version", "2.0")
        .debug("engine", "PostgreSQL")
        .field("table_count", self.tables.len())
        .alt("production");

      // Build a custom tree structure that doesn't match the data layout
      for (i, table_name) in self.tables.iter().enumerate() {
        let mut table_builder = crate::Builder::new();
        table_builder
          .name("Table")
          .field("name", table_name)
          .alt(format!("table_{i}")) // Show table identifier
          .debug("id", i)
          .debug("schema", "public");

        // Add metadata if available
        if let Some(meta) = self.metadata.get(&i) {
          table_builder
            .field("metadata", meta)
            .debug("source", "config");
        }

        // Add relationships as child nodes (flexible structure)
        let related_tables: Vec<String> = self
          .relationships
          .iter()
          .filter(|(from, ..)| *from == i)
          .map(|(_, to, rel_type)| {
            format!("{} -> {}", rel_type, self.tables[*to])
          })
          .collect();

        if !related_tables.is_empty() {
          for (j, relation) in related_tables.iter().enumerate() {
            let mut rel_builder = crate::Builder::new();
            rel_builder
              .name("Relationship")
              .field("type", relation)
              .alt("FK") // Show it's a foreign key
              .debug("index", j)
              .debug("enforced", "true");
            table_builder
              .add_named_builder(&format!("relation_{j}"), rel_builder);
          }
        }

        b.add_named_builder(&format!("table_{i}"), table_builder);
      }
    }
  }

  let mut metadata = std::collections::HashMap::new();
  metadata.insert(0, "Primary user table".to_string());
  metadata.insert(1, "Order tracking".to_string());

  let db = Database {
    tables: vec![
      "users".to_string(),
      "orders".to_string(),
      "products".to_string(),
    ],
    relationships: vec![
      (1, 0, "belongs_to".to_string()), // orders -> users
      (1, 2, "has_many".to_string()),   // orders -> products
    ],
    metadata,
  };

  let mut p = Printer::default();
  p.render(&db);
  let output = p.strip_color();

  // Verify the key features are present
  assert!(output.contains("Database")); // Main structure
  assert!(output.contains("production")); // Alt value
  assert!(output.contains("[version: 2.0]")); // Debug info
  assert!(output.contains("[engine: PostgreSQL]")); // Multiple debug entries
  assert!(output.contains("Table")); // Custom tree structure
  assert!(output.contains("table_0")); // Alt values for tables
  assert!(output.contains("belongs_to -> users")); // Flexible relationships
  assert!(output.contains("FK")); // Alt for relationships
  assert!(output.contains("[enforced: true]")); // Debug for relationships
  assert!(output.contains("Primary user table")); // Metadata
}
