use std::{fmt::Debug, vec};

use crate::oc::{Error, error::ErrorKind, lexer};

use super::*;
use nom::Finish;

fn compare_ast<T: Debug>(actual: T, expected: impl Into<T>)
{
  assert_eq!(format!("{:?}", actual), format!("{:?}", expected.into()));
}

fn compare_parse_to_ast<T, P>(s: &str, parser: P, expected: impl Into<T>)
where
  T: Debug,
  for<'a> P: nom::Parser<Input<'a>, Output = T, Error = Error>,
{
  let r: T = parse(s, parser);
  compare_ast(r, expected);
}

/// Unwrap a nom result in tests with a readable error message
pub(crate) fn unwrap_nom<'a, T>(input: Input<'a>, result: OCPResult<Input<'a>, T>) -> T
{
  match result.finish()
  {
    Ok((remaining, value)) =>
    {
      if !remaining.0.is_empty()
      {
        panic!(
          "\nParse error: unexpected remaining input: {:?}",
          remaining.0
        );
      }
      value
    }
    Err(e) =>
    {
      panic!("\nParse error:\n{}", e.nice_error_string(input.1.source));
    }
  }
}

fn parse<T, P>(s: &str, mut parser: P) -> T
where
  for<'a> P: nom::Parser<Input<'a>, Output = T, Error = Error>,
{
  let ctx = Context::new(s);
  let tokens = lexer::tokenize(s).expect("tokenization failed");
  let input = Input(&tokens, &ctx);
  unwrap_nom(input.clone(), parser.parse(input))
}

fn parse_fail<T, P>(s: &str, mut parser: P, f: impl Fn(&ErrorKind) -> bool)
where
  T: std::fmt::Debug,
  for<'a> P: nom::Parser<Input<'a>, Output = T, Error = Error>,
{
  let ctx = Context::new(s);
  let tokens = lexer::tokenize(s).expect("tokenization failed");
  let input = Input(&tokens, &ctx);
  let result = parser.parse(input);
  match result
  {
    Ok(r) =>
    {
      if r.0.is_empty()
      {
        panic!(
          "Expected parsing of '{s}' to fail, but it succeeded: {:#?}",
          r.1
        );
      }
      else
      {
        panic!(
          "Unexpected error for '{s}': it failed with remaining input: {:?}",
          r.0
        );
      }
    }
    Err(nom::Err::Failure(e)) | Err(nom::Err::Error(e)) =>
    {
      if !f(e.error_kind())
      {
        panic!("Unexpected error for '{s}': got {:?}", e);
      }
    }
    Err(e) => panic!("Unexpected parsing error for '{s}': got {:?}", e),
  }
}

fn parse_str<P>(s: &str, mut parser: P, exp: &str)
where
  for<'a> P: nom::Parser<Input<'a>, Output = &'a str, Error = Error>,
{
  let ctx = Context::new(s);
  let tokens = lexer::tokenize(s).expect("tokenization failed");
  let input = Input(&tokens, &ctx);
  assert_eq!(unwrap_nom(input.clone(), parser.parse(input)), exp);
}

#[test]
fn test_oc_parse_context_variable_identity()
{
  let ctx = Context::new("");

  let a1 = ctx.identifiers.create_variable_from_name("a");
  let a2 = ctx.identifiers.create_variable_from_name("a");
  let b = ctx.identifiers.create_variable_from_name("b");

  assert_eq!(a1.id, a2.id);
  assert_eq!(a1.name, "a");
  assert_ne!(a1.id, b.id);
}

#[test]
fn test_oc_parse_ident()
{
  parse_str("abc123_", ident, "abc123_");
  parse_str("null", ident, "null");
}

#[test]
fn test_oc_parse_parameter_name()
{
  parse_str("$param_1", parameter_name, "$param_1");
}

#[test]
fn test_oc_parse_string_literal_double_quote()
{
  parse_str(r#""hello world""#, string_literal, "hello world");
}

#[test]
fn test_oc_parse_string_literal_single_quote()
{
  parse_str("'hello world'", string_literal, "hello world");
  parse_str("', as#?lßdj '", string_literal, ", as#?lßdj ");
}
#[test]
fn test_oc_parse_string_literal_backtick()
{
  parse_str("`hello`", string_literal, "hello");
}

#[test]
fn test_oc_parse_int_literal()
{
  let value = parse("42", int);
  assert_eq!(value, 42);

  let value = parse("0x162CD4F6", int);
  assert_eq!(value, 0x162CD4F6);

  let value = parse("0o2613152366", int);
  assert_eq!(value, 0o2613152366);

  compare_parse_to_ast(
    "-42",
    expression,
    ast::Value {
      value: (-42).into(),
    },
  );

  compare_parse_to_ast("+42", expression, ast::Value { value: 42.into() });
  compare_parse_to_ast("123", expression, ast::Value { value: 123.into() });

  compare_parse_to_ast(
    "0x162CD4F6",
    expression,
    ast::Value {
      value: 0x162CD4F6.into(),
    },
  );

  let value = parse("0o2613152366", expression);
  let expected = ast::Expression::Value(ast::Value {
    value: 0o2613152366.into(),
  });
  compare_ast(value, expected);

  parse_fail("0o1000000000000000000000", expression, |e| {
    matches!(e, ErrorKind::IntegerOverflow)
  });
}

#[test]
fn test_oc_parse_float_literal()
{
  let value = parse("3.1315", num);
  assert!((value - 3.1315).abs() < 1e-6);
  let value = parse("2E-01", num);
  assert!((value - 2E-01).abs() < 1e-6);

  let value = parse(".1e-5", expression);

  let expected = ast::Expression::Value(ast::Value {
    value: 0.1e-5_f64.into(),
  });

  compare_ast(value, expected);

  parse_fail("1.34E999", num, |e| {
    matches!(e, ErrorKind::FloatingPointOverflow)
  });
}

#[test]
fn test_oc_parse_labels()
{
  compare_ast(
    parse(":A|B|C", labels),
    ast::LabelExpression::Or(vec![
      ast::LabelExpression::String("A".to_string()),
      ast::LabelExpression::String("B".to_string()),
      ast::LabelExpression::String("C".to_string()),
    ]),
  );
  compare_ast(
    parse(":A|:B", labels),
    ast::LabelExpression::Or(vec![
      ast::LabelExpression::String("A".to_string()),
      ast::LabelExpression::String("B".to_string()),
    ]),
  );
  compare_ast(
    parse(":A:B:C", labels),
    ast::LabelExpression::And(vec![
      ast::LabelExpression::String("A".to_string()),
      ast::LabelExpression::String("B".to_string()),
      ast::LabelExpression::String("C".to_string()),
    ]),
  );
}

#[test]
fn test_oc_parse_member_access()
{
  compare_parse_to_ast(
    "n.name.first",
    member_access,
    ast::MemberAccess {
      left: ast::Variable {
        identifier: ast::VariableIdentifier {
          name: "n".to_string(),
          id: 0,
        },
      }
      .into(),
      path: vec!["name".to_string(), "first".to_string()],
    },
  );
}

#[test]
fn test_oc_parse_index_access()
{
  compare_parse_to_ast(
    "arr[0]",
    array_access,
    ast::IndexAccess {
      left: ast::Variable {
        identifier: ast::VariableIdentifier {
          name: "arr".to_string(),
          id: 0,
        },
      }
      .into(),
      index: ast::Value { value: 0.into() }.into(),
    },
  );
}

#[test]
fn test_oc_parse_range_access()
{
  compare_parse_to_ast(
    "arr[1..2]",
    array_access,
    ast::RangeAccess {
      left: ast::Variable {
        identifier: ast::VariableIdentifier {
          name: "arr".to_string(),
          id: 0,
        },
      }
      .into(),
      start: Some(ast::Value { value: 1.into() }.into()),
      end: Some(ast::Value { value: 2.into() }.into()),
    },
  );
}

#[test]
fn test_oc_parse_function_call()
{
  compare_parse_to_ast(
    "func(1, 2)",
    primary,
    ast::FunctionCall {
      name: "func".to_string(),
      arguments: vec![
        ast::Expression::Value(ast::Value { value: 1.into() }),
        ast::Expression::Value(ast::Value { value: 2.into() }),
      ],
    },
  );
}

#[test]
fn test_oc_parse_function_call_all()
{
  compare_parse_to_ast(
    "func(*)",
    primary,
    ast::FunctionCall {
      name: "func".to_string(),
      arguments: vec![ast::Expression::Value(ast::Value { value: 0.into() })],
    },
  );
}

#[test]
fn test_oc_parse_relational_binary_operator()
{
  compare_parse_to_ast(
    "a = b",
    expression,
    ast::RelationalEqual {
      left: ast::Variable {
        identifier: ast::VariableIdentifier {
          name: "a".to_string(),
          id: 0,
        },
      }
      .into(),
      right: ast::Variable {
        identifier: ast::VariableIdentifier {
          name: "b".to_string(),
          id: 1,
        },
      }
      .into(),
    },
  );

  compare_parse_to_ast(
    "a < b",
    relational,
    ast::RelationalInferior {
      left: ast::Variable {
        identifier: ast::VariableIdentifier {
          name: "a".to_string(),
          id: 0,
        },
      }
      .into(),
      right: ast::Variable {
        identifier: ast::VariableIdentifier {
          name: "b".to_string(),
          id: 1,
        },
      }
      .into(),
    },
  );

  compare_parse_to_ast(
    "id(a) = id(b)",
    relational,
    ast::RelationalEqual {
      left: ast::FunctionCall {
        name: "id".to_string(),
        arguments: vec![
          ast::Variable {
            identifier: ast::VariableIdentifier {
              name: "a".to_string(),
              id: 0,
            },
          }
          .into(),
        ],
      }
      .into(),
      right: ast::FunctionCall {
        name: "id".to_string(),
        arguments: vec![
          ast::Variable {
            identifier: ast::VariableIdentifier {
              name: "b".to_string(),
              id: 1,
            },
          }
          .into(),
        ],
      }
      .into(),
    },
  );
}

#[test]
fn test_oc_parse_node_pattern_simple()
{
  let actual = parse("(n:Person)", node_pattern);

  let expected = ast::NodePattern {
    variable: ast::VariableIdentifier {
      name: "n".to_string(),
      id: 0, // assumes first variable created gets id 0
    }
    .into(),
    labels: ast::LabelExpression::String("Person".to_string()),
    properties: None,
  };
  compare_ast(actual, expected);
}

#[test]
fn test_oc_parse_node_pattern_parameters()
{
  let node = parse("(n {id: 12, name: 'foo'})", node_pattern);

  let expected_node = ast::NodePattern {
    variable: ast::VariableIdentifier {
      name: "n".to_string(),
      id: 0, // assumes first variable created gets id 0
    }
    .into(),
    labels: ast::LabelExpression::None,
    properties: Some(ast::Expression::Map(ast::Map {
      map: vec![
        (
          "id".to_string(),
          ast::Expression::Value(ast::Value { value: 12.into() }),
        ),
        (
          "name".to_string(),
          ast::Expression::Value(ast::Value {
            value: "foo".into(),
          }),
        ),
      ],
    })),
  };

  compare_ast(node, expected_node);

  let node = parse(r#"(n {id: "12", name: "foo"})"#, node_pattern);

  let expected_node = ast::NodePattern {
    variable: ast::VariableIdentifier {
      name: "n".to_string(),
      id: 0, // assumes first variable created gets id 0
    }
    .into(),
    labels: ast::LabelExpression::None,
    properties: Some(ast::Expression::Map(ast::Map {
      map: vec![
        (
          "id".to_string(),
          ast::Expression::Value(ast::Value { value: "12".into() }),
        ),
        (
          "name".to_string(),
          ast::Expression::Value(ast::Value {
            value: "foo".into(),
          }),
        ),
      ],
    })),
  };

  compare_ast(node, expected_node);
}

#[test]
fn test_oc_parse_edge_patterns()
{
  compare_parse_to_ast(
    "(n)-[m]->(p)",
    patterns::<AllowUndirected>,
    vec![
      ast::EdgePattern {
        source: ast::NodePattern {
          variable: ast::VariableIdentifier {
            name: "n".to_string(),
            id: 0,
          }
          .into(),
          labels: ast::LabelExpression::None,
          properties: None,
        },
        variable: ast::VariableIdentifier {
          name: "m".to_string(),
          id: 2,
        }
        .into(),
        destination: ast::NodePattern {
          variable: ast::VariableIdentifier {
            name: "p".to_string(),
            id: 1,
          }
          .into(),
          labels: ast::LabelExpression::None,
          properties: None,
        },
        directivity: graphcore::EdgeDirectivity::Directed,
        labels: ast::LabelExpression::None,
        properties: None,
        recursive_range: None,
      }
      .into(),
    ],
  );

  parse_fail("(n)<-[m]->(p)", patterns::<DisallowUndirected>, |e| {
    matches!(e, ErrorKind::RequiresDirectedEdge)
  });
  parse_fail("(n)-[m]-(p)", patterns::<DisallowUndirected>, |e| {
    matches!(e, ErrorKind::RequiresDirectedEdge)
  });
  parse_fail("(n)->[m]<-(p)", patterns::<DisallowUndirected>, |e| {
    matches!(e, ErrorKind::ConflictingEdgeDirections)
  });
  parse_fail("(a)-[:A|:B]->(b)", patterns::<DisallowUndirected>, |e| {
    matches!(e, ErrorKind::NoSingleRelationshipType)
  });
}

#[test]
fn test_oc_parse_edge_range()
{
  compare_parse_to_ast("*1..", edge_range, 1..usize::MAX);
  compare_parse_to_ast("*1..3", edge_range, 1..3);
  compare_parse_to_ast("*..3", edge_range, 0..3);
}

#[test]
fn test_oc_parse_edge_recursive_patterns()
{
  compare_parse_to_ast(
    "(n)-[m*1..2]->(p)",
    patterns::<AllowUndirected>,
    vec![
      ast::EdgePattern {
        source: ast::NodePattern {
          variable: ast::VariableIdentifier {
            name: "n".to_string(),
            id: 0,
          }
          .into(),
          labels: ast::LabelExpression::None,
          properties: None,
        },
        variable: ast::VariableIdentifier {
          name: "m".to_string(),
          id: 2,
        }
        .into(),
        destination: ast::NodePattern {
          variable: ast::VariableIdentifier {
            name: "p".to_string(),
            id: 1,
          }
          .into(),
          labels: ast::LabelExpression::None,
          properties: None,
        },
        directivity: graphcore::EdgeDirectivity::Directed,
        labels: ast::LabelExpression::None,
        properties: None,
        recursive_range: Some(1..2),
      }
      .into(),
    ],
  );
}

#[test]
fn test_oc_parse_array_literal()
{
  compare_parse_to_ast(
    "['male', 'female', null]",
    array_literal,
    ast::Array {
      array: vec![
        ast::Expression::Value(ast::Value {
          value: "male".into(),
        }),
        ast::Expression::Value(ast::Value {
          value: "female".into(),
        }),
        ast::Expression::Value(ast::Value {
          value: graphcore::Value::Null,
        }),
      ],
    },
  );

  compare_parse_to_ast("[]", array_literal, ast::Array { array: vec![] });

  compare_parse_to_ast(
    "[[]]",
    array_literal,
    ast::Array {
      array: vec![ast::Array { array: vec![] }.into()],
    },
  );

  compare_parse_to_ast(
    "[null, 2]",
    array_literal,
    ast::Array {
      array: vec![
        ast::Expression::Value(ast::Value {
          value: graphcore::Value::Null,
        }),
        ast::Expression::Value(ast::Value { value: 2.into() }),
      ],
    },
  );

  compare_parse_to_ast(
    "[null, [ ' a ', ' ' ], ' [ a ', ' [ ], ] ', ' [ ', [ ' ' ], ' ] ' ]",
    array_literal,
    ast::Array {
      array: vec![
        ast::Expression::Value(ast::Value {
          value: graphcore::Value::Null,
        }),
        ast::Array {
          array: vec![
            ast::Expression::Value(ast::Value {
              value: " a ".into(),
            }),
            ast::Expression::Value(ast::Value { value: " ".into() }),
          ],
        }
        .into(),
        ast::Expression::Value(ast::Value {
          value: " [ a ".into(),
        }),
        ast::Expression::Value(ast::Value {
          value: " [ ], ] ".into(),
        }),
        ast::Expression::Value(ast::Value {
          value: " [ ".into(),
        }),
        ast::Array {
          array: vec![ast::Expression::Value(ast::Value { value: " ".into() })],
        }
        .into(),
        ast::Expression::Value(ast::Value {
          value: " ] ".into(),
        }),
      ],
    },
  );
}

#[test]
fn test_oc_parse_map_literal()
{
  let node = parse("{id:12, name: 'foo'}", map_literal);

  let expected = ast::Expression::Map(ast::Map {
    map: vec![
      (
        "id".to_string(),
        ast::Expression::Value(ast::Value { value: 12.into() }),
      ),
      (
        "name".to_string(),
        ast::Expression::Value(ast::Value {
          value: "foo".into(),
        }),
      ),
    ],
  });

  compare_ast(node, expected);
}

#[test]
fn test_oc_parse_map_pair()
{
  let node = parse("id:12", map_pair);

  let expected_pair = (
    "id".to_string(),
    ast::Expression::Value(ast::Value { value: 12.into() }),
  );

  assert_eq!(node, expected_pair);
}

#[test]
fn test_oc_parse_null()
{
  let value = parse("null", expression);

  let expected = ast::Expression::Value(ast::Value {
    value: graphcore::Value::Null,
  });

  compare_ast(value, expected);
}

#[test]
fn test_oc_parse_booleans()
{
  let value = parse("true", primary);

  let expected = ast::Expression::Value(ast::Value { value: true.into() });

  compare_ast(value, expected);
  let value = parse("false", primary);

  let expected = ast::Expression::Value(ast::Value {
    value: false.into(),
  });

  compare_ast(value, expected);
}

#[test]
fn test_oc_parse_string_literal()
{
  let value = parse("'foo'", expression);

  let expected = ast::Expression::Value(ast::Value {
    value: "foo".into(),
  });

  compare_ast(vec![value], vec![expected]);

  let value = parse("''", expression);

  let expected = ast::Expression::Value(ast::Value { value: "".into() });

  compare_ast(vec![value], vec![expected]);
}
#[test]
fn test_oc_parse_node_pattern_with_parameter_properties()
{
  let node = parse("(n $props)", node_pattern);

  let expr = node.properties.expect("properties expected");

  match expr
  {
    ast::Expression::Parameter(p) => assert_eq!(p.name, "$props"),
    _ => panic!("expected parameter expression"),
  }
}

#[test]
fn test_oc_parse_match_statement_without_where()
{
  let actual = parse("MATCH (n)", match_statement);

  let expected = ast::Statement::Match(ast::Match {
    patterns: vec![
      ast::NodePattern {
        variable: ast::VariableIdentifier {
          name: "n".to_string(),
          id: 0,
        }
        .into(),
        labels: ast::LabelExpression::None,
        properties: None,
      }
      .into(),
    ],
    optional: false,
    where_expression: None,
    search: None,
  });

  compare_ast(actual, expected);
}

#[test]
fn test_oc_parse_optional_match_statement_without_where()
{
  let actual = parse("OPTIONAL MATCH (n)", match_statement);

  let expected = ast::Statement::Match(ast::Match {
    patterns: vec![
      ast::NodePattern {
        variable: ast::VariableIdentifier {
          name: "n".to_string(),
          id: 0,
        }
        .into(),
        labels: ast::LabelExpression::None,
        properties: None,
      }
      .into(),
    ],
    optional: true,
    where_expression: None,
    search: None,
  });

  compare_ast(actual, expected);
}

#[test]
fn test_oc_parse_match_statement_double_edge()
{
  let actual = parse("MATCH ()-->()-->()", match_statement);

  let expected = ast::Statement::Match(ast::Match {
    patterns: vec![
      ast::EdgePattern {
        source: ast::NodePattern {
          variable: None,
          labels: ast::LabelExpression::None,
          properties: None,
        },
        destination: ast::NodePattern {
          variable: Some(ast::VariableIdentifier {
            name: "anonymous_0".to_string(),
            id: 0,
          }),
          labels: ast::LabelExpression::None,
          properties: None,
        },
        variable: None,
        directivity: graphcore::EdgeDirectivity::Directed,
        labels: ast::LabelExpression::None,
        properties: None,
        recursive_range: None,
      }
      .into(),
      ast::EdgePattern {
        source: ast::NodePattern {
          variable: Some(ast::VariableIdentifier {
            name: "anonymous_0".to_string(),
            id: 0,
          }),
          labels: ast::LabelExpression::None,
          properties: None,
        },
        destination: ast::NodePattern {
          variable: None,
          labels: ast::LabelExpression::None,
          properties: None,
        },
        variable: None,
        directivity: graphcore::EdgeDirectivity::Directed,
        labels: ast::LabelExpression::None,
        properties: None,
        recursive_range: None,
      }
      .into(),
    ],
    optional: false,
    where_expression: None,
    search: None,
  });

  compare_ast(actual, expected);
}

#[test]
fn test_oc_parse_create_node_statement()
{
  compare_parse_to_ast(
    "CREATE (n)",
    create_statement,
    ast::Create {
      patterns: vec![
        ast::NodePattern {
          variable: ast::VariableIdentifier {
            name: "n".to_string(),
            id: 0,
          }
          .into(),
          labels: ast::LabelExpression::None,
          properties: None,
        }
        .into(),
      ],
    },
  );
  compare_parse_to_ast(
    "CREATE ()",
    create_statement,
    ast::Create {
      patterns: vec![
        ast::NodePattern {
          variable: None,
          labels: ast::LabelExpression::None,
          properties: None,
        }
        .into(),
      ],
    },
  );
  compare_parse_to_ast(
    "CREATE (:A)",
    create_statement,
    ast::Create {
      patterns: vec![
        ast::NodePattern {
          variable: None,
          labels: ast::LabelExpression::String("A".to_string()),
          properties: None,
        }
        .into(),
      ],
    },
  );
  compare_parse_to_ast(
    "CREATE (:A:B)",
    create_statement,
    ast::Create {
      patterns: vec![
        ast::NodePattern {
          variable: None,
          labels: ast::LabelExpression::And(vec![
            ast::LabelExpression::String("A".to_string()),
            ast::LabelExpression::String("B".to_string()),
          ]),
          properties: None,
        }
        .into(),
      ],
    },
  );
}

#[test]
fn test_oc_parse_create_edge_statements()
{
  compare_parse_to_ast(
    "CREATE ()-[:R]->()",
    create_statement,
    ast::Create {
      patterns: vec![
        ast::EdgePattern {
          source: ast::NodePattern {
            variable: None,
            labels: ast::LabelExpression::None,
            properties: None,
          },
          destination: ast::NodePattern {
            variable: None,
            labels: ast::LabelExpression::None,
            properties: None,
          },
          variable: None,
          directivity: graphcore::EdgeDirectivity::Directed,
          labels: ast::LabelExpression::String("R".to_string()),
          properties: None,
          recursive_range: None,
        }
        .into(),
      ],
    },
  );
  compare_parse_to_ast(
    "CREATE (a)<-[:FOO]-(b)",
    create_statement,
    ast::Create {
      patterns: vec![
        ast::EdgePattern {
          source: ast::NodePattern {
            variable: Some(ast::VariableIdentifier {
              name: "b".to_string(),
              id: 1,
            }),
            labels: ast::LabelExpression::None,
            properties: None,
          },
          destination: ast::NodePattern {
            variable: Some(ast::VariableIdentifier {
              name: "a".to_string(),
              id: 0,
            }),
            labels: ast::LabelExpression::None,
            properties: None,
          },
          variable: None,
          directivity: graphcore::EdgeDirectivity::Directed,
          labels: ast::LabelExpression::String("FOO".to_string()),
          properties: None,
          recursive_range: None,
        }
        .into(),
      ],
    },
  );
  parse_fail("CREATE (a)-[:FOO]-(b)", create_statement, |e| {
    matches!(e, ErrorKind::RequiresDirectedEdge)
  });
  parse_fail("CREATE (a)<-[:FOO]->(b)", create_statement, |e| {
    matches!(e, ErrorKind::RequiresDirectedEdge)
  });
}

#[test]
fn test_oc_parse_return_star()
{
  let stmt = parse("RETURN *", return_statement);

  match stmt
  {
    ast::Statement::Return(r) =>
    {
      assert!(r.all);
    }
    _ => panic!("expected RETURN statement"),
  }
}

#[test]
fn test_oc_parse_return_n()
{
  let stmt = parse("RETURN n", return_statement);

  let exppected_stmt = ast::Statement::Return(ast::Return {
    all: false,
    expressions: vec![ast::NamedExpression {
      identifier: ast::VariableIdentifier {
        name: "n".to_string(),
        id: 0,
      },
      expression: ast::Variable {
        identifier: ast::VariableIdentifier {
          name: "n".to_string(),
          id: 0,
        },
      }
      .into(),
    }],
    where_expression: None,
    modifiers: Default::default(),
  });
  compare_ast(vec![stmt], vec![exppected_stmt]);
}

#[test]
fn test_oc_parse_return_n_m()
{
  let stmt = parse("RETURN n, m", return_statement);

  let exppected_stmt = ast::Statement::Return(ast::Return {
    all: false,
    expressions: vec![
      ast::NamedExpression {
        identifier: ast::VariableIdentifier {
          name: "n".to_string(),
          id: 0,
        },
        expression: ast::Variable {
          identifier: ast::VariableIdentifier {
            name: "n".to_string(),
            id: 0,
          },
        }
        .into(),
      },
      ast::NamedExpression {
        identifier: ast::VariableIdentifier {
          name: "m".to_string(),
          id: 1,
        },
        expression: ast::Variable {
          identifier: ast::VariableIdentifier {
            name: "m".to_string(),
            id: 1,
          },
        }
        .into(),
      },
    ],
    where_expression: None,
    modifiers: Default::default(),
  });
  compare_ast(vec![stmt], vec![exppected_stmt]);
}

#[test]
fn test_oc_parse_return_n_p_1()
{
  let stmt = parse("RETURN n+1", return_statement);

  let expected_stmt = ast::Statement::Return(ast::Return {
    all: false,
    expressions: vec![ast::NamedExpression {
      identifier: ast::VariableIdentifier {
        name: "n+1".to_string(),
        id: 1,
      },
      expression: ast::Addition {
        left: ast::Variable {
          identifier: ast::VariableIdentifier {
            name: "n".to_string(),
            id: 0,
          },
        }
        .into(),
        right: ast::Value { value: 1.into() }.into(),
      }
      .into(),
    }],
    where_expression: None,
    modifiers: Default::default(),
  });
  compare_ast(vec![stmt], vec![expected_stmt]);
}

#[test]
fn test_oc_parse_return_n_as_p()
{
  let stmt = parse("RETURN n AS p", return_statement);

  let expected_stmt = ast::Statement::Return(ast::Return {
    all: false,
    expressions: vec![ast::NamedExpression {
      identifier: ast::VariableIdentifier {
        name: "p".to_string(),
        id: 1,
      },
      expression: ast::Variable {
        identifier: ast::VariableIdentifier {
          name: "n".to_string(),
          id: 0,
        },
      }
      .into(),
    }],
    where_expression: None,
    modifiers: Default::default(),
  });
  compare_ast(vec![stmt], vec![expected_stmt]);
}

#[test]
fn test_oc_parse_return_n_name_as_p()
{
  let stmt = parse("RETURN n.name AS p", return_statement);

  let expected_stmt = ast::Statement::Return(ast::Return {
    all: false,
    expressions: vec![ast::NamedExpression {
      identifier: ast::VariableIdentifier {
        name: "p".to_string(),
        id: 1,
      },
      expression: ast::MemberAccess {
        left: ast::Variable {
          identifier: ast::VariableIdentifier {
            name: "n".to_string(),
            id: 0,
          },
        }
        .into(),
        path: vec!["name".to_string()],
      }
      .into(),
    }],
    where_expression: None,
    modifiers: Default::default(),
  });
  compare_ast(vec![stmt], vec![expected_stmt]);
}

#[test]
fn test_oc_parse_return_n_modifiers()
{
  compare_parse_to_ast(
    "RETURN n LIMIT -1",
    return_statement,
    ast::Statement::Return(ast::Return {
      all: false,
      expressions: vec![ast::NamedExpression {
        identifier: ast::VariableIdentifier {
          name: "n".to_string(),
          id: 0,
        },
        expression: ast::Variable {
          identifier: ast::VariableIdentifier {
            name: "n".to_string(),
            id: 0,
          },
        }
        .into(),
      }],
      where_expression: None,
      modifiers: ast::Modifiers {
        order_by: None,
        limit: Some(ast::Expression::Value(ast::Value { value: (-1).into() })),
        skip: None,
      },
    }),
  );
}

#[test]
fn test_oc_parse_with_n_m()
{
  let stmt = parse("WITH n, m", with_statement);

  let exppected_stmt = ast::With {
    all: false,
    expressions: vec![
      ast::NamedExpression {
        identifier: ast::VariableIdentifier {
          name: "n".to_string(),
          id: 0,
        },
        expression: ast::Variable {
          identifier: ast::VariableIdentifier {
            name: "n".to_string(),
            id: 0,
          },
        }
        .into(),
      },
      ast::NamedExpression {
        identifier: ast::VariableIdentifier {
          name: "m".to_string(),
          id: 1,
        },
        expression: ast::Variable {
          identifier: ast::VariableIdentifier {
            name: "m".to_string(),
            id: 1,
          },
        }
        .into(),
      },
    ],
    where_expression: None,
    modifiers: Default::default(),
  }
  .into();
  compare_ast(vec![stmt], vec![exppected_stmt]);
}

#[test]
fn test_oc_parse_with_n_as_m()
{
  compare_parse_to_ast(
    "WITH n AS m",
    with_statement,
    ast::With {
      all: false,
      expressions: vec![ast::NamedExpression {
        identifier: ast::VariableIdentifier {
          name: "m".to_string(),
          id: 1,
        },
        expression: ast::Variable {
          identifier: ast::VariableIdentifier {
            name: "n".to_string(),
            id: 0,
          },
        }
        .into(),
      }],
      where_expression: None,
      modifiers: Default::default(),
    },
  );
}

#[test]
fn test_oc_parse_modifiers()
{
  compare_parse_to_ast(
    "ORDER BY n.name DESC, n.age ASC LIMIT 4 SKIP 2",
    modifiers,
    ast::Modifiers {
      order_by: Some(ast::OrderBy {
        expressions: vec![
          ast::OrderByExpression {
            asc: false,
            expression: ast::MemberAccess {
              left: ast::Variable {
                identifier: ast::VariableIdentifier {
                  name: "n".to_string(),
                  id: 0,
                },
              }
              .into(),
              path: vec!["name".to_string()],
            }
            .into(),
          },
          ast::OrderByExpression {
            asc: true,
            expression: ast::MemberAccess {
              left: ast::Variable {
                identifier: ast::VariableIdentifier {
                  name: "n".to_string(),
                  id: 0,
                },
              }
              .into(),
              path: vec!["age".to_string()],
            }
            .into(),
          },
        ],
      }),
      limit: Some(ast::Expression::Value(ast::Value { value: 4.into() })),
      skip: Some(ast::Expression::Value(ast::Value { value: 2.into() })),
    },
  );
  compare_parse_to_ast(
    "ORDER BY n.name DESC, n.age LIMIT 4",
    modifiers,
    ast::Modifiers {
      order_by: Some(ast::OrderBy {
        expressions: vec![
          ast::OrderByExpression {
            asc: false,
            expression: ast::MemberAccess {
              left: ast::Variable {
                identifier: ast::VariableIdentifier {
                  name: "n".to_string(),
                  id: 0,
                },
              }
              .into(),
              path: vec!["name".to_string()],
            }
            .into(),
          },
          ast::OrderByExpression {
            asc: true,
            expression: ast::MemberAccess {
              left: ast::Variable {
                identifier: ast::VariableIdentifier {
                  name: "n".to_string(),
                  id: 0,
                },
              }
              .into(),
              path: vec!["age".to_string()],
            }
            .into(),
          },
        ],
      }),
      limit: Some(ast::Expression::Value(ast::Value { value: 4.into() })),
      skip: None,
    },
  );
  compare_parse_to_ast(
    "LIMIT -1",
    modifiers,
    ast::Modifiers {
      order_by: None,
      limit: Some(ast::Expression::Value(ast::Value { value: (-1).into() })),
      skip: None,
    },
  );
}

#[test]
fn test_oc_parse_with_modifiers()
{
  compare_parse_to_ast(
    "WITH n ORDER BY n.name DESC, n.age LIMIT 4",
    with_statement,
    ast::With {
      all: false,
      expressions: vec![ast::NamedExpression {
        identifier: ast::VariableIdentifier {
          name: "n".to_string(),
          id: 0,
        },
        expression: ast::Variable {
          identifier: ast::VariableIdentifier {
            name: "n".to_string(),
            id: 0,
          },
        }
        .into(),
      }],
      where_expression: None,
      modifiers: ast::Modifiers {
        order_by: Some(ast::OrderBy {
          expressions: vec![
            ast::OrderByExpression {
              asc: false,
              expression: ast::MemberAccess {
                left: ast::Variable {
                  identifier: ast::VariableIdentifier {
                    name: "n".to_string(),
                    id: 0,
                  },
                }
                .into(),
                path: vec!["name".to_string()],
              }
              .into(),
            },
            ast::OrderByExpression {
              asc: true,
              expression: ast::MemberAccess {
                left: ast::Variable {
                  identifier: ast::VariableIdentifier {
                    name: "n".to_string(),
                    id: 0,
                  },
                }
                .into(),
                path: vec!["age".to_string()],
              }
              .into(),
            },
          ],
        }),
        limit: Some(ast::Expression::Value(ast::Value { value: 4.into() })),
        skip: None,
      },
    },
  );
}

#[test]
fn test_oc_parse_unwind_n_as_m()
{
  compare_parse_to_ast(
    "UNWIND n AS m",
    unwind_statement,
    ast::Unwind {
      identifier: ast::VariableIdentifier {
        name: "m".to_string(),
        id: 1,
      },
      expression: ast::Variable {
        identifier: ast::VariableIdentifier {
          name: "n".to_string(),
          id: 0,
        },
      }
      .into(),
    },
  );
}

#[test]
fn test_oc_parse_queries_multiple_statements()
{
  let qs = parse("MATCH (n) RETURN *", queries);

  let expected = vec![vec![
    ast::Match {
      patterns: vec![
        ast::NodePattern {
          variable: ast::VariableIdentifier {
            name: "n".to_string(),
            id: 0,
          }
          .into(),
          labels: ast::LabelExpression::None,
          properties: None,
        }
        .into(),
      ],
      optional: false,
      where_expression: None,
      search: None,
    }
    .into(),
    ast::Statement::Return(ast::Return {
      all: true,
      expressions: Default::default(),
      where_expression: None,
      modifiers: Default::default(),
    }),
  ]];

  compare_ast(qs, expected);
}

#[test]
fn test_oc_parse_queries_multiple_queries()
{
  let qs = parse("MATCH (n); RETURN *", queries);

  let expected = vec![
    vec![
      ast::Match {
        patterns: vec![
          ast::NodePattern {
            variable: ast::VariableIdentifier {
              name: "n".to_string(),
              id: 0,
            }
            .into(),
            labels: ast::LabelExpression::None,
            properties: None,
          }
          .into(),
        ],
        optional: false,
        where_expression: None,
        search: None,
      }
      .into(),
    ],
    vec![ast::Statement::Return(ast::Return {
      all: true,
      expressions: Default::default(),
      where_expression: None,
      modifiers: Default::default(),
    })],
  ];

  compare_ast(qs, expected);
}

#[test]
fn test_oc_parse_delete_statement()
{
  compare_parse_to_ast(
    "DELETE n, m",
    delete_statement,
    ast::Delete {
      detach: false,
      expressions: vec![
        ast::Variable {
          identifier: ast::VariableIdentifier {
            name: "n".to_string(),
            id: 0,
          },
        }
        .into(),
        ast::Variable {
          identifier: ast::VariableIdentifier {
            name: "m".to_string(),
            id: 1,
          },
        }
        .into(),
      ],
    },
  );

  compare_parse_to_ast(
    "DETACH DELETE n, m",
    delete_statement,
    ast::Delete {
      detach: true,
      expressions: vec![
        ast::Variable {
          identifier: ast::VariableIdentifier {
            name: "n".to_string(),
            id: 0,
          },
        }
        .into(),
        ast::Variable {
          identifier: ast::VariableIdentifier {
            name: "m".to_string(),
            id: 1,
          },
        }
        .into(),
      ],
    },
  );
}

#[test]
fn test_oc_parse_set_statement()
{
  compare_parse_to_ast(
    "SET n.name = 'Alice', n.age = 30",
    set_statement,
    ast::Update {
      updates: vec![
        ast::OneUpdate::SetProperty(ast::UpdateProperty {
          target: ast::VariableIdentifier {
            name: "n".into(),
            id: 0,
          },
          path: vec!["name".into()],
          expression: ast::Expression::Value(ast::Value {
            value: "Alice".into(),
          }),
        }),
        ast::OneUpdate::SetProperty(ast::UpdateProperty {
          target: ast::VariableIdentifier {
            name: "n".into(),
            id: 0,
          },
          path: vec!["age".into()],
          expression: ast::Expression::Value(ast::Value { value: 30.into() }),
        }),
      ],
    },
  );

  compare_parse_to_ast(
    "SET a += {num: 42}",
    set_statement,
    ast::Update {
      updates: vec![ast::OneUpdate::AddProperty(ast::UpdateProperty {
        target: ast::VariableIdentifier {
          name: "a".into(),
          id: 0,
        },
        path: vec![],
        expression: ast::Expression::Map(ast::Map {
          map: vec![(
            "num".to_string(),
            ast::Expression::Value(ast::Value { value: 42.into() }),
          )],
        }),
      })],
    },
  );
}

#[test]
fn test_oc_parse_remove_statement()
{
  compare_parse_to_ast(
    "REMOVE n:Person:Employee",
    remove_statement,
    ast::Update {
      updates: vec![ast::OneUpdate::RemoveLabels(ast::AddRemoveLabels {
        target: ast::VariableIdentifier {
          name: "n".to_string(),
          id: 0,
        },
        labels: vec!["Person".into(), "Employee".into()],
      })],
    },
  );
}

#[test]
fn test_oc_call_internal_stats()
{
  compare_parse_to_ast(
    "CALL gqlite.internal.stats()",
    call_statement,
    ast::Call {
      name: "gqlite.internal.stats".to_string(),
      arguments: vec![],
    },
  );
}

#[test]
fn test_oc_parse_match_with_search_basic()
{
  let actual = parse(
    "MATCH (n:Doc) SEARCH n IN (VECTOR INDEX idx FOR $q LIMIT 5)",
    match_statement,
  );

  assert!(matches!(actual, ast::Statement::Match(ref m) if m.search.is_some()));
  if let ast::Statement::Match(m) = actual
  {
    let search = m.search.unwrap();
    assert_eq!(search.variable.name(), "n");
    assert_eq!(search.index, "idx");
    assert_eq!(search.limit, 5);
    assert!(matches!(search.query, ast::Expression::Parameter(_)));
    assert!(search.score_alias.is_none());
  }
}

#[test]
fn test_oc_parse_match_with_search_and_score()
{
  let actual = parse(
    "MATCH (n:Doc) SEARCH n IN (VECTOR INDEX idx FOR $q LIMIT 10) SCORE AS score",
    match_statement,
  );

  assert!(matches!(actual, ast::Statement::Match(ref m) if m.search.is_some()));
  if let ast::Statement::Match(m) = actual
  {
    let search = m.search.unwrap();
    assert_eq!(search.variable.name(), "n");
    assert_eq!(search.index, "idx");
    assert_eq!(search.limit, 10);
    assert!(search.score_alias.is_some());
    assert_eq!(search.score_alias.unwrap().name(), "score");
  }
}

#[test]
fn test_oc_parse_match_without_search()
{
  let actual = parse("MATCH (n:Doc) WHERE n.id > 5", match_statement);

  assert!(matches!(actual, ast::Statement::Match(ref m) if m.search.is_none()));
}

#[test]
fn test_oc_parse_match_with_search_and_where()
{
  let actual = parse(
    "MATCH (n:Doc) WHERE n.active = true SEARCH n IN (VECTOR INDEX idx FOR $q LIMIT 5)",
    match_statement,
  );

  assert!(
    matches!(actual, ast::Statement::Match(ref m) if m.search.is_some() && m.where_expression.is_some())
  );
}

#[test]
fn test_oc_parse_search_variable_not_in_pattern()
{
  // This should fail because 'm' is not in the MATCH pattern
  let query = "MATCH (n:Doc) SEARCH m IN (VECTOR INDEX idx FOR $q LIMIT 5)";
  let ctx = Context::new(query);
  let tokens = lexer::tokenize(query).expect("tokenization failed");
  let input = Input(&tokens, &ctx);

  let result = match_statement(input);
  assert!(result.is_err());
}

#[test]
fn test_oc_parse_create_vector_index_basic()
{
  let actual = parse("CREATE VECTOR INDEX doc_idx ON :Doc (embedding)", statement);

  assert!(matches!(actual, ast::Statement::CreateVectorIndex(_)));
  if let ast::Statement::CreateVectorIndex(cvi) = actual
  {
    assert_eq!(cvi.name, "doc_idx");
    assert_eq!(cvi.label, "Doc");
    assert_eq!(cvi.property, "embedding");
    assert!(cvi.dimension.is_none());
    assert!(cvi.metric.is_none());
  }
}

#[test]
fn test_oc_parse_create_vector_index_with_options()
{
  let actual = parse(
    "CREATE VECTOR INDEX idx ON :Node (vec) OPTIONS { dimension: 384, metric: \"cosine\" }",
    statement,
  );

  assert!(matches!(actual, ast::Statement::CreateVectorIndex(_)));
  if let ast::Statement::CreateVectorIndex(cvi) = actual
  {
    assert_eq!(cvi.name, "idx");
    assert_eq!(cvi.label, "Node");
    assert_eq!(cvi.property, "vec");
    assert_eq!(cvi.dimension, Some(384));
    assert_eq!(cvi.metric, Some(ast::Metric::Cosine));
  }
}

#[test]
fn test_oc_parse_create_vector_index_l2_metric()
{
  let actual = parse(
    "CREATE VECTOR INDEX emb_idx ON :Entity (embedding) OPTIONS { dimension: 768, metric: \"l2\" }",
    statement,
  );

  assert!(matches!(actual, ast::Statement::CreateVectorIndex(_)));
  if let ast::Statement::CreateVectorIndex(cvi) = actual
  {
    assert_eq!(cvi.metric, Some(ast::Metric::L2));
    assert_eq!(cvi.dimension, Some(768));
  }
}

#[test]
fn test_oc_parse_create_vector_index_dotproduct_metric()
{
  let actual = parse(
    "CREATE VECTOR INDEX dot_idx ON :Data (vector) OPTIONS { metric: \"dot_product\" }",
    statement,
  );

  assert!(matches!(actual, ast::Statement::CreateVectorIndex(_)));
  if let ast::Statement::CreateVectorIndex(cvi) = actual
  {
    assert_eq!(cvi.metric, Some(ast::Metric::DotProduct));
  }
}

#[test]
fn test_oc_parse_create_vector_index_dimension_only()
{
  let actual = parse(
    "CREATE VECTOR INDEX idx ON :Doc (emb) OPTIONS { dimension: 512 }",
    statement,
  );

  assert!(matches!(actual, ast::Statement::CreateVectorIndex(_)));
  if let ast::Statement::CreateVectorIndex(cvi) = actual
  {
    assert_eq!(cvi.dimension, Some(512));
    assert!(cvi.metric.is_none());
  }
}
