test_that("can create a schedule entry from a single well-documented fun", {
  res <- build_schedule_entry(
    test_path("test_pipelines/test_pipeline_daily_good.R")
  )
  expect_s3_class(res, "MaestroPipelineList")
})

test_that("can create a schedule entry from a default tagged fun", {
  res <- build_schedule_entry(
    test_path("test_pipelines/test_pipeline_daily_default.R")
  )
  expect_s3_class(res, "MaestroPipelineList")
  expect_equal(length(res$MaestroPipelines), 1)
})

test_that("invalid tags trigger error", {
  build_schedule_entry(
    test_path("test_pipelines/test_pipeline_daily_bad.R")
  ) |>
    expect_error(regexp = "Invalid maestroFrequency")
}) |>
  suppressMessages()

test_that("can create a schedule entry from a multi-function script", {
  res <- build_schedule_entry(
    test_path("test_pipelines/test_multi_fun_pipeline.R")
  )
  expect_s3_class(res, "MaestroPipelineList")
  expect_equal(length(res$MaestroPipelines), 2)
}) |>
  suppressMessages()

test_that("Errors on a pipeline with no tagged functions", {
  build_schedule_entry(
    test_path("test_pipelines_parse_all_bad/test_pipeline_no_func.R")
  ) |>
    expect_error(regexp = "tags present in")
}) |>
  suppressMessages()

test_that("Errors on pipeline that doesn't use functions and returns null", {

  expect_error({
    res <- build_schedule_entry(
      test_path("test_pipelines_parse_all_bad/tagged_but_no_func.R")
    )
  }, regexp = "has tags but no function")
}) |>
  suppressMessages()

test_that("Errors on pipeline with isolated tags", {
  expect_error({
    res <- build_schedule_entry(
      test_path("test_pipelines_parse_all_bad/tagged_but_no_func_multi.R")
    )
  }, regexp = "has tags but no function")
})

test_that("Informative error on parsing schedule entry with an error", {
  expect_error({
    res <- build_schedule_entry(
      test_path("test_pipelines_parse_all_bad/test_pipeline_script_with_error.R")
    )
  }, regexp = "Could not build")
})

test_that("Script with untagged function isn't treated as a scheduled pipeline", {
  res <- build_schedule_entry(
    test_path("test_pipelines_parse_all_good/pipe_with_custom_fun.R")
  )
  expect_equal(length(res$MaestroPipelines), 1)
})

test_that("Script with good specifiers parses well", {
  expect_no_error({
    schedule <- build_schedule_entry(
      test_path("test_pipelines_parse_all_good/specifiers.R")
    )
  })
})

test_that("Script with bad specifiers errors", {
  schedule <- build_schedule_entry(
    test_path("test_pipelines_parse_all_bad/specifiers.R")
  ) |>
    expect_error(regexp = "pipeline must have")
})

test_that("Pipeline with inputs checks for .input", {
  schedule <- build_schedule_entry(
    test_path("test_pipelines_parse_all_bad/dags.R")
  ) |>
    expect_error(regexp = "pipeline must have a parameter")

  expect_no_error({
    schedule <- build_schedule_entry(
      test_path("test_pipelines_dags_good/dags.R")
    )
  })
})

test_that("Errors if pipeline self-references as input", {
  schedule <- build_schedule_entry(
    test_path("test_pipelines_parse_all_bad/dags_self_reference_inputs.R")
  ) |>
    expect_error(regexp = "cannot contain self-references")
})

test_that("Errors if pipeline self-references as output", {
  schedule <- build_schedule_entry(
    test_path("test_pipelines_parse_all_bad/dags_self_reference_outputs.R")
  ) |>
    expect_error(regexp = "cannot contain self-references")
})

test_that("Pipelines with maestro tag get picked up", {
  res <- build_schedule_entry(
    test_path("test_pipelines/test_pipeline_maestro.R")
  )
  expect_equal(length(res$MaestroPipelines), 1)
})

test_that("Pipelines with maestroHours and maestroFrequency = 1 hour are valid", {

  withr::with_tempdir({
    dir.create("pipelines")
    writeLines(
      "
      #' @maestroTz America/Halifax
      #' @maestroFrequency 1 hour
      #' @maestroStartTime 2025-01-01 00:00:00
      #' @maestroHours 0 4 7 12 18
      hourly <- function() {

      }
      ",
      con = "pipelines/hourly.R"
    )

    expect_no_error({
      build_schedule_entry("pipelines/hourly.R")
    })
  })
})

test_that("Pipelines with maestroStartTime in HH:MM:SS and a multi-unit frequency fail", {

  withr::with_tempdir({
    dir.create("pipelines")
    writeLines(
      "
      #' @maestroTz America/Halifax
      #' @maestroFrequency 2 days
      #' @maestroStartTime 00:00:00
      hourly <- function() {

      }
      ",
      con = "pipelines/hourly.R"
    )

    expect_error({
      build_schedule_entry("pipelines/hourly.R")
    }, regexp = "Cannot use a `@maestroStartTime`")
  })

  withr::with_tempdir({
    dir.create("pipelines")
    writeLines(
      "
      #' @maestroTz America/Halifax
      #' @maestroFrequency weekly
      #' @maestroStartTime 00:00:00
      hourly <- function() {

      }
      ",
      con = "pipelines/hourly.R"
    )

    expect_error({
      build_schedule_entry("pipelines/hourly.R")
    }, regexp = "`@maestroStartTime`")
  })
})

test_that("Minutely frequency can be combined with maestroHours and other specifiers", {

  withr::with_tempdir({
    dir.create("pipelines")
    writeLines(
      "
      #' @maestroFrequency 10 minutes
      #' @maestroHours 5
      minutely <- function() {

      }
      ",
      con = "pipelines/minutely.R"
    )

    expect_no_error(build_schedule_entry("pipelines/minutely.R"))
  })
})

test_that("maestroMap with multiple space-separated expressions is stored as character vector", {

  withr::with_tempdir({
    dir.create("pipelines")
    writeLines(
      c(
        "#' @maestroFrequency daily",
        "upstream <- function() {",
        "  list(ids = 1:3, labels = c('a', 'b', 'c'))",
        "}",
        "",
        "#' @maestroInputs upstream",
        "#' @maestroMap .input$ids .input$labels",
        "downstream <- function(.input) {",
        "  paste(.input$ids, .input$labels)",
        "}"
      ),
      con = "pipelines/multi_iter.R"
    )

    entry <- build_schedule_entry("pipelines/multi_iter.R")
    map <- entry$MaestroPipelines[[2]]$get_map()
    expect_equal(map, c(".input$ids", ".input$labels"))
    expect_length(map, 2L)
  })
})

test_that("maestroMap with invalid R expression errors at build_schedule_entry", {

  withr::with_tempdir({
    dir.create("pipelines")
    # Write the file directly to avoid roxygen2 interpreting escape sequences
    # in a roxygen comment string. Use a genuinely unparseable fragment.
    writeLines(
      c(
        "#' @maestroFrequency daily",
        "upstream <- function() 1:3",
        "",
        "#' @maestroInputs upstream",
        "#' @maestroMap .input$ids 1+",
        "downstream <- function(.input) .input"
      ),
      con = "pipelines/bad_iter.R"
    )

    expect_error(
      build_schedule_entry("pipelines/bad_iter.R"),
      regexp = "invalid R expression"
    )
  })
})
