test_that("run_schedule works on different kinds of frequencies", {

  schedule <- build_schedule(test_path("test_pipelines_run_all_good"))

  test_freqs <- c("14 days", "10 minutes", "25 mins", "1 week",
                  "1 quarter", "12 months", "1 years", "24 hours",
                  "31 days", "1 secs", "50 seconds", "daily", "hourly", "weekly", "yearly")

  purrr::walk(test_freqs, ~{
    expect_no_error({
      run_schedule(
        schedule,
        orch_frequency = .x
      ) |>
        suppressWarnings()
    })
  })
}) |>
  suppressMessages()

test_that("run_schedule errors if check_datetime is not a timestamp", {

  schedule <- build_schedule(test_path("test_pipelines_run_all_good")) |>
    suppressMessages()

  expect_error(
    run_schedule(
      schedule,
      check_datetime = "a"
    ),
    regexp = "must be a"
  )

  # But Dates work
  expect_no_error(
    run_schedule(
      schedule,
      orch_frequency = "hourly",
      check_datetime = as.Date("2024-01-01")
    )
  )
}) |>
  suppressMessages()

test_that("maestro.check_datetime_override option overrides check_datetime", {

  schedule <- build_schedule(test_path("test_pipelines_run_all_good")) |>
    suppressMessages()

  override_dt <- as.POSIXct("2025-06-01 09:00:00", tz = "UTC")

  withr::with_options(
    list(maestro.check_datetime_override = override_dt),
    {
      # Argument value is ignored; option takes precedence without error
      expect_no_error(
        run_schedule(
          schedule,
          orch_frequency = "hourly",
          check_datetime = Sys.time()
        ) |>
          suppressMessages()
      )
    }
  )
})

test_that("maestro.check_datetime_override errors if not POSIXct", {

  schedule <- build_schedule(test_path("test_pipelines_run_all_good")) |>
    suppressMessages()

  withr::with_options(
    list(maestro.check_datetime_override = "not-a-datetime"),
    {
      expect_error(
        run_schedule(schedule, orch_frequency = "hourly") |>
          suppressMessages(),
        regexp = "must be a"
      )
    }
  )
})

test_that("run_schedule with quiet=TRUE prints no messages", {
  schedule <- build_schedule(test_path("test_pipelines_run_all_good")) |>
    suppressMessages()

  expect_no_message({
    run_schedule(schedule, orch_frequency = "hourly", run_all = TRUE, quiet = TRUE)
  })

  expect_type(last_run_messages(), "list")
  expect_true(rlang::is_named(last_run_messages()))
  expect_gt(length(last_run_messages()), 0)
})

test_that("run_schedule is idempotent", {
  
  schedule <- build_schedule(test_path("test_pipelines_run_all_good"), quiet = TRUE)

  run_schedule(
    schedule,
    orch_frequency = "15 minutes",
    check_datetime = as.POSIXct("2024-04-25 09:35:00", tz = "UTC"),
    quiet = TRUE
  )

  status1 <- schedule$get_status()

  run_schedule(
    schedule,
    orch_frequency = "15 minutes",
    check_datetime = as.POSIXct("2024-04-25 09:45:00", tz = "UTC"),
    quiet = TRUE
  )

  status2 <- schedule$get_status()

  expect_equal(nrow(status1), nrow(status2))
})

test_that("run_schedule timeliness checks - pipelines run when they're supposed to", {

  withr::with_tempdir({
    dir.create("pipelines")
    writeLines(
      "
      #' @maestroFrequency 1 day
      #' @maestroStartTime 2024-01-01
      #' @maestroTz America/Halifax
      get_mtcars <- function() { mtcars }

      #' @maestroFrequency 3 months
      #' @maestroStartTime 2024-01-01
      wait <- function() {}

      #' @maestroFrequency 1 week
      #' @maestroStartTime 2024-01-01
      weekly <- function() {}

      #' @maestroFrequency 1 day
      #' @maestroStartTime 2024-01-01
      way_in_the_future <- function() {}

      #' @maestroFrequency weekly
      #' @maestroStartTime 2024-01-01
      weekly2 <- function() {}

      #' @maestroFrequency 1 hour
      #' @maestroStartTime 2024-03-01 09:35:00
      #' @maestroTz UTC
      get_mtcars2 <- function() { mtcars }
      ",
      con = "pipelines/pipes.R"
    )

    schedule <- build_schedule(quiet = TRUE)
    output <- run_schedule(
      schedule,
      orch_frequency = "15 minutes",
      check_datetime = as.POSIXct("2024-04-25 09:35:00", tz = "UTC"),
      quiet = TRUE
    )
    expect_equal(output$get_status()$invoked, c(FALSE, FALSE, FALSE, FALSE, FALSE, TRUE))

    schedule <- build_schedule(quiet = TRUE)
    output <- run_schedule(
      schedule,
      orch_frequency = "hourly",
      check_datetime = as.POSIXct("2024-03-02 09:00:00", tz = "America/Halifax"),
      quiet = TRUE
    )
    expect_equal(output$get_status()$invoked, c(FALSE, FALSE, FALSE, FALSE, FALSE, TRUE))
  })
})

test_that("run_schedule timeliness checks - specifiers (e.g., hours, days, months)", {

  withr::with_tempdir({
    dir.create("pipelines")
    writeLines(
      "
      #' @maestroFrequency daily
      #' @maestroStartTime 2024-01-01
      #' @maestroDays 1 8 24 28
      specific_days <- function() {}

      #' @maestroFrequency hourly
      #' @maestroStartTime 2024-01-01
      #' @maestroHours 0 4 8 12 16 20
      specific_hours <- function() {}

      #' @maestroFrequency biweekly
      #' @maestroStartTime 2024-01-01
      #' @maestroMonths 1 5 10
      specific_months1 <- function() {}

      #' @maestroFrequency hourly
      #' @maestroStartTime 2024-01-01
      #' @maestroDays Mon Wed Fri
      specific_days2 <- function() {}

      #' @maestroFrequency hourly
      #' @maestroStartTime 2024-01-01
      #' @maestroDays Sun Sat
      specific_days3 <- function() {}

      #' @maestroFrequency monthly
      #' @maestroStartTime 2024-01-01
      #' @maestroMonths 1 5 10
      specific_months3 <- function() {}

      #' @maestroFrequency hourly
      #' @maestroStartTime 2024-01-01
      #' @maestroHours 1 12
      #' @maestroDays 1
      #' @maestroMonths 1 5 10
      specific_multi <- function() {}
      ",
      con = "pipelines/specifiers.R"
    )

    schedule <- build_schedule(quiet = TRUE)
    output <- run_schedule(
      schedule,
      orch_frequency = "hourly",
      check_datetime = as.POSIXct("2024-04-01 00:00:00", tz = "UTC"), # Monday
      quiet = TRUE
    )
    expect_equal(
      output$get_status()$invoked,
      c(TRUE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE)
    )

    schedule <- build_schedule(quiet = TRUE)
    output <- run_schedule(
      schedule,
      orch_frequency = "hourly",
      check_datetime = as.POSIXct("2024-05-01 00:00:00", tz = "UTC"), # Wednesday
      quiet = TRUE
    )
    expect_equal(
      output$get_status()$invoked,
      c(TRUE, TRUE, FALSE, TRUE, FALSE, TRUE, FALSE)
    )
  })
})

test_that("run_schedule timeliness checks - specifiers on a non UTC timezone", {

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

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

    schedule <- build_schedule(quiet = TRUE)

    run_schedule(
      schedule,
      orch_frequency = "1 hour",
      check_datetime = as.POSIXct("2025-01-01 07:00:00", tz = "America/Halifax"),
      quiet = TRUE
    )

    status <- get_status(schedule)
    expect_true(status$invoked[status$pipe_name == "non_utc_hours"])

    run_schedule(
      schedule,
      orch_frequency = "1 hour",
      check_datetime = as.POSIXct("2025-01-01 07:00:00", tz = "UTC"),
      quiet = TRUE
    )

    status <- get_status(schedule)
    expect_false(status$invoked[status$pipe_name == "non_utc_hours"])
  })
})

test_that("run_schedule timeliness checks - non UTC daylight savings ", {

  withr::with_tempdir({
    dir.create("pipelines")
    writeLines(
      "
      #' @maestroTz America/Halifax
      #' @maestroFrequency daily
      #' @maestroStartTime 2025-03-07 00:00:00
      non_utc <- function() {

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

    schedule <- build_schedule(quiet = TRUE)

    run_schedule(
      schedule,
      orch_frequency = "1 hour",
      check_datetime = as.POSIXct("2025-03-10 00:00:00", tz = "America/Halifax"),
      quiet = TRUE
    )

    status <- get_status(schedule)
    expect_true(status$invoked[status$pipe_name == "non_utc"])
  })

  withr::with_tempdir({
    dir.create("pipelines")
    writeLines(
      "
      #' @maestroTz America/Halifax
      #' @maestroFrequency hourly
      #' @maestroStartTime 2025-03-07 00:00:00
      #' @maestroHours 4
      non_utc <- function() {

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

    schedule <- build_schedule(quiet = TRUE)

    run_schedule(
      schedule,
      orch_frequency = "1 hour",
      check_datetime = as.POSIXct("2025-03-10 04:00:00", tz = "America/Halifax"),
      quiet = TRUE
    )

    status <- get_status(schedule)
    expect_true(status$invoked[status$pipe_name == "non_utc"])
  })

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

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

    schedule <- build_schedule(quiet = TRUE)

    run_schedule(
      schedule,
      orch_frequency = "1 hour",
      check_datetime = as.POSIXct("2025-03-08 04:00:00", tz = "America/Halifax"),
      quiet = TRUE
    )

    status <- get_status(schedule)
    expect_true(status$invoked[status$pipe_name == "non_utc"])
  })

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

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

    schedule <- build_schedule(quiet = TRUE)

    run_schedule(
      schedule,
      orch_frequency = "1 hour",
      check_datetime = as.POSIXct("2025-03-11 00:00:00", tz = "UTC"),
      quiet = TRUE
    )

    status <- get_status(schedule)
    expect_true(status$invoked[status$pipe_name == "non_utc"])
  })
})

test_that("run_schedule propagates warnings", {

  schedule <- build_schedule(test_path("test_pipelines_run_two_warnings"))

  expect_message({
    run_schedule(
      schedule,
      run_all = TRUE
    )
  })
  expect_gt(length(last_run_warnings()), 0)
}) |>
  suppressMessages()

test_that("run_schedule handles errors in a pipeline", {

  schedule <- build_schedule(test_path("test_pipelines_run_some_errors"))

  withr::with_tempfile("log", {
    expect_message({
      output <- run_schedule(schedule, run_all = TRUE, log_to_file = log)
    })
    status <- output$get_status()

    log_len_t1 <- length(readLines(log))
    expect_gt(log_len_t1, 0)

    errors <- last_run_errors()
    expect_type(errors, "list")
    expect_length(errors, 1)
    expect_true(all(!is.na(status$pipeline_started)))

    # Run again to ensure log file is appended to
    run_schedule(schedule, run_all = TRUE, log_to_file = log)
    log_len_t2 <- length(readLines(log))
    expect_gt(log_len_t2, log_len_t1)
  })
}) |>
  suppressMessages()

test_that("run_schedule creates maestro.log if log_to_file is TRUE", {

  withr::with_tempdir({
    dir.create("pipelines")
    writeLines(
      "
      #' @maestroFrequency hourly
      test <- function() {

      }
      ",
      con = "pipelines/test.R"
    )
    schedule <- build_schedule()
    run_schedule(schedule, run_all = TRUE, log_to_file = TRUE)
    expect_true(file.exists("maestro.log"))
  })
}) |>
  suppressMessages()

test_that("run_schedule doesn't create maestro.log if log_to_file is FALSE", {

  withr::with_tempdir({
    dir.create("pipelines")
    writeLines(
      "
      #' @maestroFrequency hourly
      test <- function() {

      }
      ",
      con = "pipelines/test.R"
    )
    schedule <- build_schedule()
    run_schedule(schedule, run_all = TRUE, log_to_file = FALSE)
    expect_true(!file.exists("maestro.log"))
  })
}) |>
  suppressMessages()

test_that("run_schedule checks type of schedule", {
  expect_error(
    run_schedule(iris, run_all = TRUE),
    regexp = "Schedule must be an object of"
  )
}) |>
  suppressMessages()

test_that("errors on invalid orch_frequency", {

  schedule <- build_schedule(test_path("test_pipelines_run_all_good"), quiet = TRUE)

  expect_error({
    run_schedule(schedule, orch_frequency = "1 potato")
  }, regexp = "Invalid `orch_frequency`")

  expect_error({
    run_schedule(schedule, orch_frequency = "56 weeks")
  }, regexp = "Invalid `orch_frequency`")
})

test_that("errors if resources are unnamed or non unique", {

  schedule <- build_schedule(test_path("test_pipelines_run_all_good"), quiet = TRUE)

  expect_error({
    run_schedule(schedule, orch_frequency = "hourly", resources = list(1))
  }, regexp = "All elements")

  expect_error({
    run_schedule(schedule, orch_frequency = "hourly", resources = list(a = 1, a = 2))
  }, regexp = "All elements")
})

test_that("warns if the orch frequency is less than the highest pipe frequency", {

  schedule <- build_schedule(test_path("test_pipelines_run_all_good"), quiet = TRUE)
  expect_warning({
    run_schedule(schedule, orch_frequency = "daily")
  })

  # Unless run_all = TRUE
  expect_no_warning({
    run_schedule(schedule, orch_frequency = "daily", run_all = TRUE)
  })
}) |>
  suppressMessages()

test_that("errors if orch_frequency is less than 1 year", {
  schedule <- build_schedule(test_path("test_pipelines_run_all_good"), quiet = TRUE)
  expect_error({
    run_schedule(
      schedule,
      orch_frequency = "2 years"
    )
  }, regexp = "Invalid `orch_frequency`")
})

test_that("maestroStartTime with HH:MM:SS runs on the expected time", {
  withr::with_tempdir({
    dir.create("pipelines")
    writeLines(
      "
      #' @maestroFrequency 1 day
      #' @maestroStartTime 10:00:00
      hhmmss <- function() {

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

    schedule <- build_schedule(quiet = TRUE)
    run_schedule(
      schedule,
      orch_frequency = "1 hour",
      check_datetime = as.POSIXct(glue::glue("{lubridate::today()} 10:00:00"), tz = "UTC"),
      quiet = TRUE
    )
    status <- get_status(schedule)
  })

  expect_snapshot(status$invoked)
})

test_that("maestroPriority works as expected", {
  withr::with_tempdir({
    dir.create("pipelines")
    writeLines(
      "
      #' @maestroFrequency 1 day
      #' @maestroStartTime 10:00:00
      p2 <- function() {
      }

      #' @maestroFrequency 1 day
      #' @maestroStartTime 10:00:00
      #' @maestroPriority 1
      q1 <- function() {
        Sys.sleep(1)
      }
      ",
      con = "pipelines/priorities.R"
    )

    schedule <- build_schedule(quiet = TRUE)
    run_schedule(
      schedule,
      orch_frequency = "1 hour",
      check_datetime = as.POSIXct("2025-03-17 10:00:00", tz = "UTC"),
      quiet = TRUE
    )
    status <- get_status(schedule)
  })

  expect_snapshot(status$pipe_name)
})

test_that("maestroDays works on expected days", {
  withr::with_tempdir({
    dir.create("pipelines")
    writeLines(
      "
      #' test maestro pipeline
      #'
      #' @maestroFrequency daily
      #' @maestroDays Tue Thu
      #' @maestroStartTime 2026-01-19
      #' @maestroLogLevel INFO

      test <- function() {
      }
      ",
      con = "pipelines/test.R"
    )

    schedule <- build_schedule(quiet = TRUE)
    run_schedule(
      schedule,
      orch_frequency = "1 day",
      check_datetime = as.Date("2026-01-22"), # this is Thursday - it should run
      quiet = TRUE
    )
    status <- get_status(schedule)
    expect_true(status$invoked)

    schedule <- build_schedule(quiet = TRUE)
    run_schedule(
      schedule,
      orch_frequency = "1 day",
      check_datetime = as.Date("2026-01-23"), # this is Friday - it shouldn't run
      quiet = TRUE
    )
    status <- get_status(schedule)
    expect_false(status$invoked)
  })
})

test_that("Pipeline that prints curly brackets runs fine", {

  withr::with_tempdir({
    dir.create("pipelines")
    writeLines(
      "
      #' test maestro pipeline
      #'
      #' @maestroFrequency daily
      test <- function() {
        message('{')
        message('\n')
      }
      ",
      con = "pipelines/test.R"
    )

    schedule <- build_schedule(quiet = TRUE)
    run_schedule(
      schedule,
      orch_frequency = "1 day",
      quiet = TRUE,
      log_to_console = TRUE,
      log_to_file = TRUE
    )
    status <- get_status(schedule)
    expect_true(status$success)
  })
})

test_that("Old README example pipelines all run with daily orchestrator", {
  withr::with_tempdir({
    dir.create("pipelines")
    writeLines(
      "
      #' @maestroFrequency daily
      #' @maestroStartTime 09:00:00
      #' @maestroTz America/Halifax
      get_mtcars <- function() {
      }

      #' @maestroFrequency daily
      #' @maestroStartTime 09:00:00
      #' @maestroTz America/Halifax
      multi_rng <- function() {
      }

      #' @maestroFrequency daily
      #' @maestroStartTime 12:30:00
      #' @maestroTz America/Halifax
      my_etl <- function() {
      }
      ",
      con = "pipelines/pipes.R"
    )

    schedule <- build_schedule(quiet = TRUE)
    run_schedule(
      schedule,
      orch_frequency = "1 day",
      check_datetime = as.POSIXct("2026-04-16 11:00:00", tz = "America/Halifax"),
      quiet = TRUE
    )
    status <- get_status(schedule)
    expect_true(all(status$invoked))
  })
})

test_that("Other vignette examples", {

  withr::with_tempdir({
    dir.create("pipelines")
    writeLines(
      "
      # ./pipelines/daily_example.R
      #' daily_example maestro pipeline
      #'
      #' @maestroFrequency 1 day
      #' @maestroStartTime 09:20:00
      daily_example <- function() {
      
        # Pipeline code
      }
      ",
      con = "pipelines/daily_example.R"
    )

    schedule <- build_schedule()

    run_schedule(
      schedule,
      orch_frequency = "1 day",
      check_datetime = as.POSIXct("2024-06-20 08:00:00", tz = "UTC"),
      quiet = TRUE
    )

    expect_true(get_status(schedule)$invoked)

    run_schedule(
      schedule,
      orch_frequency = "15 minutes",
      check_datetime = as.POSIXct("2024-06-20 08:00:00", tz = "UTC"),
      quiet = TRUE
    )

    expect_false(get_status(schedule)$invoked)
  })
})

test_that("Irregular hourly with weekly pipeline", {

  withr::with_tempdir({
    dir.create("pipelines")
    writeLines(
      "
      #'
      #' @maestroFrequency weekly
      #' @maestroStartTime Fri 04:00:00
      weekly <- function() {
      
        # Pipeline code
      }
      ",
      con = "pipelines/weekly.R"
    )

    schedule <- build_schedule()

    run_schedule(
      schedule,
      orch_frequency = "4 hours",
      check_datetime = as.POSIXct("2026-05-01 04:00:00", tz = "UTC"), # this is a Friday
      quiet = TRUE
    )

    expect_true(get_status(schedule)$invoked)
  })
})

test_that("Check validity of next_run", {

  # On exact timestamp
  withr::with_tempdir({
    dir.create("pipelines")
    writeLines(
      "
      #' @maestroFrequency 1 day
      #' @maestroStartTime 03:00:00
      #' @maestroTz UTC
      my_pipe <- function() {
      }
      ",
      con = "pipelines/pipes.R"
    )

    schedule <- build_schedule(quiet = TRUE)
    run_schedule(
      schedule,
      orch_frequency = "1 hour",
      check_datetime = as.POSIXct("2026-04-16 03:00:00", tz = "UTC"),
      quiet = TRUE
    )
    status <- get_status(schedule)
    expect_equal(status$next_run, as.POSIXct("2026-04-17 03:00:00", tz = "UTC"))
  })

  # After the 12:00:00 on the previous day
  withr::with_tempdir({
    dir.create("pipelines")
    writeLines(
      "
      #' @maestroFrequency 1 day
      #' @maestroStartTime 09:00:00
      #' @maestroTz UTC
      local <- function() {

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

    schedule <- build_schedule(quiet = TRUE)
    run_schedule(
      schedule,
      orch_frequency = "1 hour",
      check_datetime = as.POSIXct("2026-04-16 14:00:00", tz = "UTC"),
      quiet = TRUE
    )
    status <- get_status(schedule)
    expect_equal(status$next_run, as.POSIXct("2026-04-17 09:00:00", tz = "UTC"))
  })
})



test_that("pipeline with unmatched argument errors gracefully", {

  withr::with_tempdir({
    dir.create("pipelines")
    writeLines(
      "
      #' @maestroFrequency daily
      needs_arg <- function(my_arg) {
        my_arg + 1
      }
      ",
      con = "pipelines/needs_arg.R"
    )

    schedule <- build_schedule(quiet = TRUE)
    run_schedule(
      schedule,
      orch_frequency = "1 day",
      run_all = TRUE,
      quiet = TRUE
    )
    status <- get_status(schedule)
    expect_false(status$success)
    expect_match(last_run_errors()$needs_arg, "missing argument `my_arg`")
  })

  withr::with_tempdir({
    dir.create("pipelines")
    writeLines(
      "
      #' @maestroFrequency daily
      needs_arg <- function(my_arg = 2) {
        my_arg + 1
      }
      ",
      con = "pipelines/needs_arg.R"
    )

    schedule <- build_schedule(quiet = TRUE)
    run_schedule(
      schedule,
      orch_frequency = "1 day",
      run_all = TRUE,
      quiet = TRUE
    )
    status <- get_status(schedule)
    expect_true(status$success)
  })
})
