test_that("`dm_flatten_to_tbl()` does the right things for 'left_join()'", {
  skip_if_src_not(c("df", "duckdb"))

  local_options(
    pillar.min_title_chars = NULL,
    pillar.max_title_chars = NULL,
    pillar.max_footer_lines = NULL,
    pillar.bold = NULL,
  )

  # FIXME: Debug GHA fail
  # for left join test the basic flattening also on all DBs
  # expect_equivalent_tbl(
  #   expect_message_obj(dm_flatten_to_tbl(dm_for_flatten(), fact)),
  #   result_from_flatten_new()
  # )

  expect_snapshot(
    {
      prepare_dm_for_flatten(
        dm_for_flatten(),
        tables = c("fact", "dim_1", "dim_2", "dim_3", "dim_4"),
        gotta_rename = TRUE
      ) %>%
        dm_get_tables()
      dm_flatten_to_tbl(dm_for_flatten(), fact)
      result_from_flatten_new()
    },
    variant = my_test_src_name
  )

  # a one-table-dm
  expect_equivalent_tbl(
    dm_for_flatten() %>%
      dm_select_tbl(fact) %>%
      dm_flatten_to_tbl(fact),
    fact()
  )

  # explicitly choose parent tables
  out <- expect_message_obj(dm_flatten_to_tbl(
    dm_for_flatten(),
    fact,
    dim_1,
    dim_2
  ))
  expect_equivalent_tbl(
    out,
    left_join(
      fact_clean_new(),
      dim_1_clean_new(),
      by = c("dim_1_key_1" = "dim_1_pk_1", "dim_1_key_2" = "dim_1_pk_2")
    ) %>%
      left_join(dim_2_clean_new(), by = c("dim_2_key" = "dim_2_pk"))
  )

  # change order of parent tables
  out <- expect_message_obj(dm_flatten_to_tbl(
    dm_for_flatten(),
    fact,
    dim_2,
    dim_1
  ))
  expect_equivalent_tbl(
    out,
    left_join(
      fact_clean_new(),
      dim_2_clean_new(),
      by = c("dim_2_key" = "dim_2_pk")
    ) %>%
      left_join(
        dim_1_clean_new(),
        by = c("dim_1_key_1" = "dim_1_pk_1", "dim_1_key_2" = "dim_1_pk_2")
      )
  )

  # with grandparent table
  expect_dm_error(
    dm_flatten_to_tbl(dm_more_complex(), tf_5, tf_4, tf_3),
    class = "only_parents"
  )

  # table unreachable
  expect_dm_error(
    dm_flatten_to_tbl(dm_for_filter(), tf_2, tf_3, tf_4),
    class = "tables_not_reachable_from_start"
  )

  # deeper hierarchy available and `auto_detect = TRUE`
  # for flatten: columns from tf_5 + tf_4 + tf_4_2 + tf_6 are combined in one table, 8 cols in total
  expect_identical(
    ncol(dm_flatten_to_tbl(dm_more_complex(), tf_5)),
    11L
  )
})

test_that("`dm_flatten_to_tbl()` does the right things for 'inner_join()'", {
  local_options(
    pillar.min_title_chars = NULL,
    pillar.max_title_chars = NULL,
    pillar.max_footer_lines = NULL,
    pillar.bold = NULL,
  )

  out <- expect_message_obj(
    arrange(
      dm_flatten_to_tbl(dm_for_flatten(), fact, .join = inner_join),
      pick(everything())
    )
  )
  # FIXME: Debug GHA fail
  # expect_equivalent_tbl(out, result_from_flatten_new())
  expect_snapshot(
    {
      out
    },
    variant = my_test_src_name
  )
})

test_that("`dm_flatten_to_tbl()` does the right things for 'full_join()'", {
  skip_if_src("sqlite")
  skip_if_src("maria")
  out <- expect_message_obj(dm_flatten_to_tbl(
    dm_for_flatten(),
    fact,
    .join = full_join
  ))
  expect_equivalent_tbl(
    out,
    fact_clean_new() %>%
      full_join(
        dim_1_clean_new(),
        by = c("dim_1_key_1" = "dim_1_pk_1", "dim_1_key_2" = "dim_1_pk_2")
      ) %>%
      full_join(dim_2_clean_new(), by = c("dim_2_key" = "dim_2_pk")) %>%
      full_join(dim_3_clean_new(), by = c("dim_3_key" = "dim_3_pk")) %>%
      full_join(dim_4_clean_new(), by = c("dim_4_key" = "dim_4_pk"))
  )
})

test_that("`dm_flatten_to_tbl()` does the right things for 'semi_join()'", {
  expect_equivalent_tbl(
    dm_flatten_to_tbl(dm_for_flatten(), fact, .join = semi_join),
    fact()
  )
})

test_that("`dm_flatten_to_tbl()` does the right things for 'anti_join()'", {
  expect_equivalent_tbl(
    dm_flatten_to_tbl(dm_for_flatten(), fact, .join = anti_join),
    fact() %>% filter(1 == 0)
  )
})

test_that("`dm_flatten_to_tbl()` does the right things for 'nest_join()'", {
  expect_dm_error(
    dm_flatten_to_tbl(dm_for_flatten(), fact, .join = nest_join),
    class = "no_flatten_with_nest_join"
  )
})


test_that("`dm_flatten_to_tbl()` does the right things for 'right_join()'", {
  skip_if_src("sqlite")
  expect_equivalent_tbl(
    expect_message_obj(expect_warning_obj(
      dm_flatten_to_tbl(dm_for_flatten(), fact, .join = right_join),
      "right_join"
    )),
    fact_clean_new() %>%
      right_join(
        dim_1_clean_new(),
        by = c("dim_1_key_1" = "dim_1_pk_1", "dim_1_key_2" = "dim_1_pk_2")
      ) %>%
      right_join(dim_2_clean_new(), by = c("dim_2_key" = "dim_2_pk")) %>%
      right_join(dim_3_clean_new(), by = c("dim_3_key" = "dim_3_pk")) %>%
      right_join(dim_4_clean_new(), by = c("dim_4_key" = "dim_4_pk"))
  )

  # change order of parent tables
  out <- expect_message_obj(dm_flatten_to_tbl(
    dm_for_flatten(),
    fact,
    dim_2,
    dim_1,
    .join = right_join
  ))
  expect_equivalent_tbl(
    out,
    right_join(
      fact_clean_new(),
      dim_2_clean_new(),
      by = c("dim_2_key" = "dim_2_pk")
    ) %>%
      right_join(
        dim_1_clean_new(),
        by = c("dim_1_key_1" = "dim_1_pk_1", "dim_1_key_2" = "dim_1_pk_2")
      )
  )
})

test_that("`dm_squash_to_tbl()` is deprecated but still works", {
  # with grandparent table
  # left_join:
  expect_deprecated(
    expect_equivalent_tbl(
      dm_squash_to_tbl(dm_more_complex(), tf_5, tf_4, tf_3),
      tf_5() %>%
        left_join(tf_4(), by = c("l" = "h")) %>%
        left_join(tf_3(), by = c("j" = "f", "j1" = "f1"))
    )
  )
})

test_that("`dm_flatten_to_tbl(.recursive = TRUE)` does the right things", {
  # with grandparent table
  # left_join:
  expect_equivalent_tbl(
    dm_flatten_to_tbl(dm_more_complex(), tf_5, tf_4, tf_3, .recursive = TRUE),
    tf_5() %>%
      left_join(tf_4(), by = c("l" = "h")) %>%
      left_join(tf_3(), by = c("j" = "f", "j1" = "f1"))
  )

  # deeper hierarchy available and `auto_detect = TRUE`
  # for flatten: columns from tf_5 + tf_4 + tf_3 + tf_4_2 + tf_6 are combined in one table, 10 cols in total
  expect_identical(
    ncol(dm_flatten_to_tbl(dm_more_complex(), tf_5, .recursive = TRUE)),
    12L
  )

  # semi_join:
  expect_dm_error(
    dm_flatten_to_tbl(dm_more_complex(), tf_5, tf_4, tf_3, .join = semi_join, .recursive = TRUE),
    class = "squash_limited"
  )

  # anti_join:
  expect_dm_error(
    dm_flatten_to_tbl(dm_more_complex(), tf_5, tf_4, tf_3, .join = anti_join, .recursive = TRUE),
    class = "squash_limited"
  )

  # fails when there is a cycle:
  expect_dm_error(
    dm_flatten_to_tbl(dm_for_filter_w_cycle(), tf_5, .recursive = TRUE),
    "no_cycles"
  )

  skip_if_src("sqlite")
  skip_if_src("maria")

  # full_join:
  expect_equivalent_tbl(
    dm_flatten_to_tbl(dm_more_complex(), tf_5, tf_4, tf_3, .join = full_join, .recursive = TRUE),
    tf_5() %>%
      full_join(tf_4(), by = c("l" = "h")) %>%
      full_join(tf_3(), by = c("j" = "f", "j1" = "f1"))
  )

  # skipping inner_join, not gaining new info

  # right_join:
  expect_dm_error(
    dm_flatten_to_tbl(dm_more_complex(), tf_5, tf_4, tf_3, .join = right_join, .recursive = TRUE),
    class = "squash_limited"
  )
})

test_that("prepare_dm_for_flatten() works", {
  # with rename
  out <- expect_message_obj(prepare_dm_for_flatten(
    dm_for_flatten(),
    c("fact", "dim_1", "dim_3"),
    gotta_rename = TRUE
  ))
  expect_equivalent_dm(
    out,
    dm_select_tbl(dm_for_flatten(), fact, dim_1, dim_3) %>% dm_disambiguate_cols(.quiet = TRUE)
  )

  # without rename
  expect_equivalent_dm(
    prepare_dm_for_flatten(dm_for_flatten(), c("fact", "dim_1", "dim_3"), gotta_rename = FALSE),
    dm_select_tbl(dm_for_flatten(), fact, dim_1, dim_3)
  )
})

test_that("tidyselect works for flatten", {
  # test if deselecting works
  expect_equivalent_tbl(
    expect_message_obj(dm_flatten_to_tbl(dm_for_flatten(), fact, -dim_2, dim_3, -dim_4, dim_1)),
    expect_message_obj(dm_flatten_to_tbl(dm_for_flatten(), fact, dim_1, dim_3))
  )

  # test if select helpers work
  expect_equivalent_tbl(
    expect_message_obj(dm_flatten_to_tbl(dm_for_flatten(), fact, ends_with("3"), ends_with("1"))),
    expect_message_obj(dm_flatten_to_tbl(dm_for_flatten(), fact, dim_3, dim_1))
  )

  expect_equivalent_tbl(
    expect_message_obj(dm_flatten_to_tbl(dm_for_flatten(), fact, everything())),
    expect_message_obj(dm_flatten_to_tbl(dm_for_flatten(), fact))
  )

  # if only deselecting one potential candidate for flattening, the tables that are not
  # candidates will generally be part of the choice
  expect_dm_error(
    dm_flatten_to_tbl(dm_for_filter(), tf_2, -tf_1),
    class = "tables_not_reachable_from_start"
  )

  # trying to deselect table that doesn't exist:
  expect_error(
    dm_flatten_to_tbl(dm_for_filter(), tf_2, -tf_101),
    class = "vctrs_error_subscript"
  )
})

test_that("`dm_join_to_tbl()` works", {
  expect_deprecated(
    expect_equivalent_tbl(
      expect_message_obj(dm_join_to_tbl(dm_for_flatten(), fact, dim_3), "Renaming"),
      left_join(
        fact_clean(),
        dim_3_clean(),
        by = c("dim_3_key" = "dim_3_pk")
      )
    )
  )

  expect_dm_error(
    expect_deprecated(dm_join_to_tbl(dm_for_filter(), tf_7, tf_8)),
    "table_not_in_dm"
  )
})

# tests that do not work on DB when keys are set ('bad_dm' and 'nycflights'; currently PG and MSSQL)
test_that("tests with 'bad_dm' work", {
  # can't create bad_dm() on Postgres due to strict constraint checks
  skip_if_src("postgres")

  # duckdb doesn't work before R 4.0
  skip_if(getRversion() < "4.0")

  # flatten bad_dm() (no referential integrity)
  if (is_db(my_test_src()) || utils::packageVersion("dplyr") >= "1.1.0.9000") {
    expect_equivalent_tbl(
      dm_flatten_to_tbl(bad_dm(), tbl_1, tbl_2, tbl_3),
      tbl_1() %>%
        left_join(tbl_2(), by = c("a" = "id", "x")) %>%
        left_join(tbl_3(), by = c("b" = "id"))
    )
  }

  skip_if_src("maria")

  # filtered `dm`
  bad_filtered_dm <- dm_filter(bad_dm(), tbl_1 = (a != 4))

  expect_equivalent_tbl(
    dm_flatten_to_tbl(bad_filtered_dm, tbl_1),
    bad_filtered_dm %>% dm_flatten_to_tbl(tbl_1)
  )

  # filtered `dm`
  expect_equivalent_tbl(
    dm_flatten_to_tbl(bad_filtered_dm, tbl_1, .join = semi_join),
    bad_filtered_dm %>% dm_flatten_to_tbl(tbl_1, .join = semi_join)
  )

  # fails when there is a cycle
  expect_dm_error(
    dm_nycflights_small() %>%
      dm_add_fk(flights, origin, airports) %>%
      dm_flatten_to_tbl(flights),
    "no_cycles"
  )
})

test_that("tests with 'bad_dm' work (2)", {
  # can't create bad_dm() on Postgres due to strict constraint checks
  skip_if_src("postgres")

  # full & right join not available on SQLite and MariaDB
  skip_if_src("sqlite", "maria")

  # duckdb doesn't work before R 4.0
  skip_if(getRversion() < "4.0")

  bad_filtered_dm <- dm_filter(bad_dm(), tbl_1 = (a != 4))

  # flatten bad_dm() (no referential integrity)
  if (is_db(my_test_src()) || utils::packageVersion("dplyr") >= "1.1.0.9000") {
    expect_equivalent_tbl(
      dm_flatten_to_tbl(bad_dm(), tbl_1, tbl_2, tbl_3, .join = full_join),
      tbl_1() %>%
        full_join(tbl_2(), by = c("a" = "id", "x")) %>%
        full_join(tbl_3(), by = c("b" = "id"))
    )
  }
})

test_that("tests with 'bad_dm' work (3)", {
  # can't create bad_dm() on Postgres due to strict constraint checks
  skip_if_src("postgres")

  # full & right join not available on SQLite
  skip_if_src("sqlite")

  # duckdb doesn't work before R 4.0
  skip_if(getRversion() < "4.0")

  bad_filtered_dm <- dm_filter(bad_dm(), tbl_1 = (a != 4))

  # flatten bad_dm() (no referential integrity)
  if (is_db(my_test_src()) || utils::packageVersion("dplyr") >= "1.1.0.9000") {
    expect_equivalent_tbl(
      dm_flatten_to_tbl(bad_dm(), tbl_1, tbl_2, tbl_3, .join = right_join),
      tbl_1() %>%
        right_join(tbl_2(), by = c("a" = "id", "x")) %>%
        right_join(tbl_3(), by = c("b" = "id"))
    )
  }

  # flatten bad_dm() (no referential integrity); different order
  if (is_db(my_test_src()) || utils::packageVersion("dplyr") >= "1.1.0.9000") {
    expect_equivalent_tbl(
      dm_flatten_to_tbl(bad_dm(), tbl_1, tbl_3, tbl_2, .join = right_join),
      tbl_1() %>%
        right_join(tbl_3(), by = c("b" = "id")) %>%
        right_join(tbl_2(), by = c("a" = "id", "x"))
    )
  }
})
