module T = ImportExpression.T
module Expr = Expression_builder
module M = Expr.Make (ImportExpression.Query.Query)
open Test_migration

let eval =
  M.eval ~path_repr:(fun formatter n -> Format.fprintf formatter "%s" n)

let test_expr ?(nested = ImportExpression.Query.QueryParameter.Literal) expr =
  let buffer = Buffer.create 16 in
  let formatter = Format.formatter_of_buffer buffer in
  let () = ImportExpression.Query.Query.observe expr formatter ~nested in
  Format.pp_print_flush formatter ();
  Buffer.contents buffer

let assert_equal expected actual =
  Alcotest.check Alcotest.string expected expected actual

let empty =
  "empty" >:: fun _ ->
  let expr = eval Expr.empty in
  let content = test_expr expr and expected = "''" in

  assert_equal expected content

let litteral =
  "literal" >:: fun _ ->
  let expr = eval Expr.literal_test in
  let content = test_expr expr and expected = "'test'" in

  assert_equal expected content

let litteral_quoted =
  "literal_quoted" >:: fun _ ->
  let expr = eval Expr.literal_quoted in
  let content = test_expr expr and expected = "'\''" in

  assert_equal expected content

let litteral_raw =
  "literal_raw" >:: fun _ ->
  let expr = eval Expr.literal_test in
  let nested = ImportExpression.Query.QueryParameter.(Raw Literal) in
  let content = test_expr expr ~nested and expected = "test" in

  assert_equal expected content

let path =
  "path" >:: fun _ ->
  (* In the path, the given function do all the job *)
  let expr = eval @@ Expr.path "test" in
  let content = test_expr expr and expected = "test" in

  assert_equal expected content

let concat =
  "concat" >:: fun _ ->
  let expr = eval Expr.concat in
  let content = test_expr expr and expected = "'' || 'test'" in

  assert_equal expected content

let nvl =
  "nvl" >:: fun _ ->
  let expr = eval @@ Expr.nvl Expr.empty Expr.literal_test in
  let content = test_expr expr and expected = "COALESCE('', 'test')" in

  assert_equal expected content

let upper =
  "upper" >:: fun _ ->
  let expr = eval @@ Expr.function' T.Upper [ Expr.literal_test ] in
  let content = test_expr expr and expected = "UPPER('test')" in

  assert_equal expected content

let join =
  "join" >:: fun _ ->
  let expr =
    ImportExpression.Query.Query.(
      join "," [ eval Expr.empty; eval Expr.literal_test ])
  in
  let content = test_expr expr and expected = "CONCAT(',', '', 'test')" in

  assert_equal expected content

let boperator_eq =
  "boperator_eq" >:: fun _ ->
  let expr = eval @@ Expr.equal Expr.empty Expr.literal_test in
  let content = test_expr expr and expected = "''='test'" in

  assert_equal expected content

let boperator_div =
  "boperator_div" >:: fun _ ->
  let expr = eval @@ Expr.divide Expr.integer_one Expr.integer_zero in
  let content = test_expr expr and expected = "CAST(1 AS REAL)/0" in

  assert_equal expected content

let boperator_neq =
  "boperator_neq" >:: fun _ ->
  let expr = eval @@ Expr.different Expr.empty Expr.literal_test in
  let content = test_expr expr and expected = "''<>'test'" in

  assert_equal expected content

let expr =
  "expr" >:: fun _ ->
  let expr = eval Expr.expr in
  let content = test_expr expr and expected = "(test NOT NULL)" in

  assert_equal expected content

let unify_int =
  "unify_int" >:: fun _ ->
  let expr = eval @@ Expr.equal (Expr.path "external") Expr.integer_zero in
  let content = test_expr expr and expected = "COALESCE(external,0)=0" in

  assert_equal expected content

let unify_string =
  "unify_string" >:: fun _ ->
  let expr = eval @@ Expr.equal (Expr.path "external") Expr.literal_zero in
  let content = test_expr expr and expected = "COALESCE(external,'')='0'" in

  assert_equal expected content

let in_string =
  "in_string" >:: fun _ ->
  let expr = eval @@ Expr.in_ (Expr.path "external") [ Expr.literal_zero ] in
  let content = test_expr expr and expected = "COALESCE(external,'') IN('0')" in
  assert_equal expected content

let not_in_string =
  "in_string" >:: fun _ ->
  let expr = eval @@ Expr.not_in (Expr.path "external") [ Expr.literal_zero ] in
  let content = test_expr expr
  and expected = "COALESCE(external,'') NOT IN('0')" in
  assert_equal expected content

(* Evaluate the max function *)
let max =
  "max" >:: fun _ ->
  let expr =
    eval @@ Expr.(max (path ":C") [ path ":A" ] [ path ":A"; path ":B" ])
  in

  let content = test_expr expr
  and expected =
    "LAST_VALUE(:C) OVER (PARTITION BY :A ORDER BY :A, :B RANGE BETWEEN \
     UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)"
  in

  assert_equal expected content

let max_no_partition =
  "max" >:: fun _ ->
  let expr = eval @@ Expr.(max (path ":C") [] [ path ":A"; path ":B" ]) in

  let content = test_expr expr
  and expected =
    "LAST_VALUE(:C) OVER (ORDER BY :A, :B RANGE BETWEEN UNBOUNDED PRECEDING \
     AND UNBOUNDED FOLLOWING)"
  in

  assert_equal expected content

let in_int =
  "in_int" >:: fun _ ->
  let expr =
    eval
    @@ Expr.in_ (Expr.path "external") [ Expr.integer_zero; Expr.integer_one ]
  in
  let content = test_expr expr and expected = "COALESCE(external,0) IN(0, 1)" in
  assert_equal expected content

let counter_no_order =
  "counter_no_order" >:: fun _ ->
  let expr = eval @@ Expr.(counter [ path ":A" ] []) in

  let content = test_expr expr
  and expected = "COUNT() OVER (PARTITION BY :A)" in

  assert_equal expected content

let counter_order =
  "counter_no_order" >:: fun _ ->
  let expr = eval @@ Expr.(counter [ path ":A" ] [ path ":B" ]) in

  let content = test_expr expr
  and expected = "ROW_NUMBER() OVER (PARTITION BY :A ORDER BY :B)" in

  assert_equal expected content

let cmp =
  "cmp" >:: fun _ ->
  let expr =
    eval
    @@ Expr.(
         function' T.Cmp
           [
             Expr.integer_zero;
             Expr.integer_one;
             Expr.integer_neg_one;
             Expr.integer_zero;
             Expr.integer_one;
           ])
  in
  let content = test_expr expr and expected = "IIF(0=1, 0, IIF(0>1, 1, -1))" in
  assert_equal expected content

let test_suit =
  [
    empty;
    litteral;
    litteral_quoted;
    litteral_raw;
    path;
    concat;
    nvl;
    upper;
    join;
    boperator_eq;
    boperator_neq;
    boperator_div;
    expr;
    unify_int;
    unify_string;
    in_string;
    not_in_string;
    in_int;
    max;
    max_no_partition;
    counter_no_order;
    counter_order;
    cmp;
  ]

let tests = "expression_query" >::: test_suit