library(testthat)

# GLOBAL TEST SETUP
data.path <- system.file("extdata", "ratings_data", package = "awkreader")

all.files <- list.files(path = data.path, full.names = TRUE)

# Defensive check: skip all tests if data isn't mounted correctly in CI/local
skip_if(length(all.files) == 0, "Mock data files not found in Data/ directory.")

the.files <- all.files[1:2]

# GROUP 1: Basic Reading and Return Types (r1 - r5)
test_that("Basic combined reads and return types work correctly", {
  # r1: combined.fread
  res_comb <- combined.fread(the.files = the.files)
  expect_s3_class(res_comb, "data.table")
  expect_true("file" %in% names(res_comb))

  # r2: return.as = "code" with NULL filter
  res_code <- filtered.fread(the.files = the.files, the.filter = NULL, skip = 0, return.as = "code")
  expect_type(res_code, "character")
  expect_match(res_code, "FNR <= 1 { next } {  print $1,$2,$3,FILENAME }", fixed = TRUE)

  # r3: nrows limit
  res_nrows <- filtered.fread(the.files = the.files, nrows = 10)
  expect_equal(nrow(res_nrows), 10)

  # r4/r5: return.as = "all"
  res_all <- filtered.fread(the.files = the.files, return.as = "all")
  expect_type(res_all, "list")
  expect_s3_class(res_all$result, "data.table")
  expect_type(res_all[[2]], "character")
})

# GROUP 2: Column Formatting and Selection (r6, r7, r9, r17, r18)
test_that("Column subsetting and filename appending behave correctly", {
  # r6: Custom file header
  res_custom_header <- filtered.fread(the.files = the.files, file.header = "the source")
  expect_true("the source" %in% names(res_custom_header))

  # r7: Omit filename
  res_no_file <- filtered.fread(the.files = the.files, include.filename = FALSE)
  expect_false("file" %in% names(res_no_file))

  # r9: Pick specific variables
  res_vars <- filtered.fread(
    the.files = the.files, the.filter = "rating == 5",
    the.variables = c("item", "rating"), include.filename = FALSE
  )
  expect_equal(names(res_vars), c("item", "rating"))

  # r17: Read all variables with "."
  res_dot <- filtered.fread(
    the.files = the.files, the.filter = "rating == 5",
    the.variables = ".", include.filename = FALSE
  )
  # Should retain original structure without file column
  expect_false("file" %in% names(res_dot))
})

# GROUP 3: Logical Operators and Math (r8, r10 - r13, r19 - r23)
test_that("AWK translation accurately parses logical operators and math", {
  # r11: AND operator (&)
  res_and <- filtered.fread(the.files = the.files, the.filter = "rating >= 3 & item == '0JFCjVx2P1RMzy3h'")
  expect_gt(nrow(res_and), 0)
  expect_true(all(res_and$rating >= 3 & res_and$item == "0JFCjVx2P1RMzy3h"))

  # r12: OR operator (|)
  res_or <- filtered.fread(the.files = the.files, the.filter = "rating == 3 | rating == 4")
  expect_gt(nrow(res_or), 0)
  expect_true(all(res_or$rating %in% c("3", "4")))

  # r13: NOT operator (!=)
  res_not <- filtered.fread(the.files = the.files, the.filter = "rating != 1 & rating != 2")
  expect_gt(nrow(res_not), 0)
  expect_true(all(!res_not$rating %in% c("1", "2")))

  # r22: Math functions (log)
  res_math <- filtered.fread(the.files = the.files, the.filter = "item == 'sFFbD3fA0Jsvs7Ic' & rating > log(rating)", return.as = "all")
  actual_command <- res_math$code

  path_1 <- shQuote(normalizePath(the.files[1], mustWork = FALSE))
  path_10 <- shQuote(normalizePath(the.files[2], mustWork = FALSE))

  expected_command <- sprintf(
    "awk 'BEGIN { FS=\",\"; OFS=\",\" } FNR <= 1 { next } { if($2 == \"sFFbD3fA0Jsvs7Ic\" && $3 > log($3)) print $1,$2,$3,FILENAME }' %s %s",
    path_1,
    path_10
  )
})

# GROUP 4: Environment Variables and Set Operators (r14 - r16, r25)
test_that("Environment variable injection and %in% / %nin% work", {

  # r16: Numeric vectors with %nin%
  res_nin <- filtered.fread(the.files = the.files, the.filter = "rating %nin% c(1:2, 4)", include.filename = FALSE)
  expect_gt(nrow(res_nin), 0)
  expect_false(any(res_nin$rating %in% c("1", "2", "4")))

})

# GROUP 5: Batch Processing (r24, r26)
test_that("Batching logic processes correctly without dropping data", {
  res_batch_code <- filtered.fread(
    the.files = the.files, the.filter = "rating >= 4",
    num.files.per.batch = 1, return.as = "code"
  )

  # If batch size is 1, and we have 2 files, the code returned should be a vector of length 2
  expect_equal(length(res_batch_code), 2)
  expect_type(res_batch_code, "character")
})

# GROUP 6: pattern.fread (r27 - r32)
test_that("pattern.fread correctly evaluates regex and connectors", {
  # r27: Simple pattern match
  res_pat1 <- pattern.fread(the.files = the.files, the.patterns = c("5PQIOK"))
  expect_gt(nrow(res_pat1), 0)

  # r28 / r29: Negative pattern match (tf = F)
  res_pat2 <- pattern.fread(the.files = the.files, the.patterns = c("5PQIOK"), tf = c(FALSE), return.as = "all")
  expect_match(res_pat2$code, "! /5PQIOK/", fixed = TRUE)

  # r30: Multiple patterns with AND
  res_pat_and <- pattern.fread(the.files = the.files, the.patterns = c("kT27T", "wBMJkot"), connectors = "and")
  if (nrow(res_pat_and) > 0) {
    expect_s3_class(res_pat_and, "data.table")
  }

  # r32: Titanic Dataset integration
  titanic_path <- file.path("../..", "Data", "Titanic.csv")
  if (file.exists(titanic_path)) {
    res_titanic <- pattern.fread(
      the.files = titanic_path,
      the.patterns = c("Female", "Child", "1st"),
      tf = c(TRUE, TRUE, FALSE),
      connectors = "and",
      file.header = "source_file"
    )

    expect_gt(nrow(res_titanic), 0)
    expect_true("source_file" %in% names(res_titanic))
  }
})

# GROUP 7: Delimiter Flexibility (delim parameter)
test_that("filtered.fread correctly handles custom delimiters", {
  # 1. Setup a temporary pipe-separated file
  tmp_file <- tempfile(fileext = ".psv")
  on.exit(unlink(tmp_file))

  writeLines(c(
    "user|item|rating",
    "user_A|item_X|5",
    "user_B|item_Y|3",
    "user_C|item_Z|5"
  ), tmp_file)

  # 2. Test data parsing with the custom delimiter
  res_delim <- filtered.fread(
    the.files = tmp_file,
    the.filter = "rating == 5",
    delim = "|",
    include.filename = FALSE
  )

  expect_s3_class(res_delim, "data.table")
  expect_equal(nrow(res_delim), 2)
  expect_equal(res_delim$user, c("user_A", "user_C"))

  # 3. Test that the AWK command accurately injects the field separator flag
  res_code_delim <- filtered.fread(
    the.files = tmp_file,
    the.filter = "rating == 5",
    delim = "|",
    return.as = "code"
  )
  expect_match(
    res_code_delim,
    "BEGIN \\{ FS=\"\\|\"; OFS=\",\" \\}",
    fixed = FALSE
  )
})

# GROUP 8: Inline Header Optimization
test_that("filtered.fread optimizes header parsing without losing column mappings", {
  # Create a mock file where data positions matter
  tmp_file1 <- tempfile(fileext = ".csv")
  tmp_file2 <- tempfile(fileext = ".csv")
  on.exit(unlink(c(tmp_file1, tmp_file2)))

  writeLines(c("user,item,rating", "A,X,5"), tmp_file1)
  writeLines(c("user,item,rating", "B,Y,2"), tmp_file2)

  # Assert that multi-file evaluation correctly tracks headers using the optimized path
  res_opt <- filtered.fread(
    the.files = c(tmp_file1, tmp_file2),
    the.filter = "rating == 5",
    include.filename = FALSE
  )

  expect_equal(nrow(res_opt), 1)
  expect_equal(res_opt$user, "A")
  expect_equal(names(res_opt), c("user", "item", "rating"))
})


test_that("record.count accurately processes numeric and string pattern skip parameters", {
  # 1. Create temporary sample data file containing metadata rows
  tmp_file <- tempfile(fileext = ".csv")

  sample_content <- c(
    "# Report Generated: 2026-05-29",
    "# Source: Academic Records Department System",
    "id,major,hw1,hw2,quiz",
    "Student_1,Statistics,87,90,94",
    "Student_2,Psychology,93,91,96",
    "Student_3,Mathematics,91,86,89",
    "Student_4,Statistics,88,85,90"
  )
  writeLines(sample_content, tmp_file)

  # Ensure the cleanup runs when the test block finishes
  on.exit(unlink(tmp_file))

  f1 <- filtered.fread(the.files = tmp_file, the.filter = "hw1 > 90", include.filename = F, skip = "quiz")
  expect_equal(nrow(f1), 2)
  f2 <- filtered.fread(the.files = tmp_file, the.filter = "hw1 > 90", include.filename = F, skip = 2)
  expect_equal(nrow(f2), 2)

  # Numeric Skip Setup (skip = 2)
  res_numeric <- record.count(the.files = tmp_file, skip = 2, include.filename = FALSE)

  expect_s3_class(res_numeric, "data.table")
  expect_equal(res_numeric$count, 4)

  # Character Pattern Skip Setup (skip = "id")
  res_string <- record.count(the.files = tmp_file, skip = "id", include.filename = FALSE)

  expect_equal(res_string$count, 4)

  # Test Case C: Filter Execution Combined with Numeric Skip
  res_filtered <- record.count(
    the.files = tmp_file,
    skip = 2,
    the.filter = "major == 'Statistics'",
    include.filename = FALSE
  )

  expect_equal(res_filtered$count, 2)

  # Invalid String Pattern Exception Throwing
  expect_error(
    record.count(the.files = tmp_file, skip = "non_existent_column_pattern"),
    "was not found in the file"
  )
})


test_that("filtered.fread accurately process numeric, charaacter and list pattern of skip parameter", {
  tmp.file <- tempfile(fileext = ".csv")
  sample.content <- c(
    "#--- START OF SYSTEM METADATA LOG ---",
    "#Generated: 2026-06-06 11:43:12",
    "#User Session: ID_884192",
    "#Server Status: Active",
    "#------------------------------------",
    "ID,Product_Name,Price,Quantity,In_Stock",
    "1,Laptop,75.07,71,FALSE",
    "2,Mouse,17.49,17,TRUE",
    "3,Keyboard,154.46,46,TRUE",
    "4,Monitor,490.58,94,TRUE",
    "5,Desk Chair,142.24,92,TRUE",
    "6,USB Cable,187.89,136,FALSE",
    "7,Headphones,878.84,9,TRUE",
    "8,Webcam,796.32,100,FALSE",
    "9,Hard Drive,970.14,18,FALSE",
    "10,Desk Lamp,961.48,109,FALSE"
  )
  writeLines(sample.content, tmp.file)

  on.exit(unlink(tmp.file))

  f1 <- filtered.fread(the.files = tmp.file, the.filter = "Price > 500", include.filename = F, skip = "In_Stock")
  expect_equal(nrow(f1), 4)
  f2 <- filtered.fread(the.files = tmp.file, the.filter = "In_Stock==TRUE", include.filename = F, skip = 5)
  expect_equal(nrow(f2), 5)
  f3 <- filtered.fread(the.files = tmp.file, the.filter = "Price > 500 & In_Stock==FALSE", include.filename = F, skip = list(skip.data.rows = 2, skip.metadata.rows = 5))
  expect_equal(nrow(f3), 3)
})




create_dummy_data <- function(base_dir, folder_name, file_name) {
  full_dir <- file.path(base_dir, folder_name)
  dir.create(full_dir, recursive = TRUE, showWarnings = FALSE)

  file_path <- file.path(full_dir, file_name)
  dummy_df <- data.frame(
    id = 1:5,
    category = c("A", "B", "A", "C", "B"),
    value = c(10, 20, 30, 40, 50)
  )
  write.csv(dummy_df, file_path, row.names = FALSE, quote = FALSE)

  return(file_path)
}

test_that("filtered.fread handles standard absolute paths (Easy)", {
  base <- tempdir()
  safe_file <- create_dummy_data(base, "easy_folder", "safe_data.csv")

  res <- filtered.fread(
    the.files = safe_file,
    the.filter = "value > 20",
    return.as = "result"
  )

  expect_true(is.data.frame(res))
  expect_equal(nrow(res), 3) # Should match rows with 30, 40 and 50
  expect_true(all(res$value > 20))
})

test_that("filtered.fread handles paths with spaces (Medium)", {
  base <- tempdir()
  space_file <- create_dummy_data(base, "folder with spaces", "data with spaces.csv")

  res <- filtered.fread(
    the.files = space_file,
    the.filter = "category == 'A'",
    return.as = "result"
  )

  expect_true(is.data.frame(res))
  expect_equal(nrow(res), 2)
  expect_true(all(res$category == "A"))
})

test_that("filtered.fread handles shell-sensitive characters in paths (Challenging)", {
  # This tests spaces, ampersands, and single quotes
  base <- tempdir()
  nasty_folder <- "Our crazy folder"
  nasty_file <- "awk_test's & file.csv"

  # Windows sometimes rejects certain chars in folder names natively,
  # so we wrap this in a tryCatch to ensure the test doesn't fail the CI
  # just because the OS blocked the file creation.
  nasty_path <- tryCatch(
    {
      create_dummy_data(base, nasty_folder, nasty_file)
    },
    error = function(e) NULL
  )

  skip_if(is.null(nasty_path), "OS does not support creating files with these characters.")

  res <- filtered.fread(
    the.files = nasty_path,
    the.filter = "value <= 30",
    return.as = "result"
  )

  expect_true(is.data.frame(res))
  expect_equal(nrow(res), 3)
})

test_that("filtered.fread produces safe cross-platform shQuote formatting", {
  # We test the command output directly to ensure shQuote is applying
  # the correct OS-level encapsulation (double quotes on Windows, single on Unix)
  base <- tempdir()
  space_file <- create_dummy_data(base, "test folder", "test.csv")

  res <- filtered.fread(
    the.files = space_file,
    return.as = "code"
  )

  # The actual execution command passed to the shell must contain the normalized, quoted path
  expected_quoted_path <- shQuote(normalizePath(space_file, mustWork = FALSE))

  # Verify the quoted path is physically present in the execution string
  expect_true(grepl(expected_quoted_path, res[1], fixed = TRUE))
})




test_that(".resolve.files handles recursive and non-recursive directory discovery", {
  # 1. Setup temporary directory structure
  tmp_dir <- file.path(tempdir(), "test_resolve_dirs")
  sub_dir <- file.path(tmp_dir, "subfolder")
  dir.create(sub_dir, recursive = TRUE, showWarnings = FALSE)
  on.exit(unlink(tmp_dir, recursive = TRUE))

  f1 <- file.path(tmp_dir, "top.csv")
  f2 <- file.path(sub_dir, "nested.csv")
  file.create(f1, f2)

  # Non-recursive should only find top.csv
  res_non_rec <- .resolve.files(tmp_dir, recursive = FALSE)
  expect_equal(length(res_non_rec), 1)
  expect_equal(normalizePath(res_non_rec), normalizePath(f1))

  # Recursive should find both files
  res_rec <- .resolve.files(tmp_dir, recursive = TRUE)
  expect_equal(length(res_rec), 2)
  expect_true(all(normalizePath(c(f1, f2)) %in% normalizePath(res_rec)))
})

test_that(".resolve.files formats file.pattern flexibly (plain string, extension, wildcard, regex)", {
  tmp_dir <- file.path(tempdir(), "test_resolve_patterns")
  dir.create(tmp_dir, showWarnings = FALSE)
  on.exit(unlink(tmp_dir, recursive = TRUE))

  f_csv  <- file.path(tmp_dir, "data.csv")
  f_txt  <- file.path(tmp_dir, "notes.txt")
  f_json <- file.path(tmp_dir, "config.json")
  file.create(f_csv, f_txt, f_json)

  # Plain extension string: "csv"
  res_plain <- .resolve.files(tmp_dir, file.pattern = "csv")
  expect_equal(basename(res_plain), "data.csv")

  # Dot extension: ".txt"
  res_dot <- .resolve.files(tmp_dir, file.pattern = ".txt")
  expect_equal(basename(res_dot), "notes.txt")

  # Wildcard glob: "*.json"
  res_glob <- .resolve.files(tmp_dir, file.pattern = "*.json")
  expect_equal(basename(res_glob), "config.json")

  # Explicit regex: "\\.(csv|txt)$"
  res_regex <- .resolve.files(tmp_dir, file.pattern = "\\.(csv|txt)$")
  expect_equal(length(res_regex), 2)
  expect_true(all(c("data.csv", "notes.txt") %in% basename(res_regex)))
})

test_that(".resolve.files correctly handles mixed directory and explicit file paths", {
  tmp_dir <- file.path(tempdir(), "test_resolve_mixed")
  dir.create(tmp_dir, showWarnings = FALSE)
  on.exit(unlink(tmp_dir, recursive = TRUE))

  f_in_dir  <- file.path(tmp_dir, "in_dir.csv")
  f_outside <- file.path(tempdir(), "outside.csv")
  file.create(f_in_dir, f_outside)
  on.exit(unlink(f_outside), add = TRUE)

  mixed_input <- c(tmp_dir, f_outside)
  res <- .resolve.files(mixed_input, file.pattern = "csv")

  expect_equal(length(res), 2)
  expect_true(all(normalizePath(c(f_in_dir, f_outside)) %in% normalizePath(res)))
})

test_that(".resolve.files deduplicates duplicate entries", {
  tmp_file <- file.path(tempdir(), "dup_test.csv")
  file.create(tmp_file)
  on.exit(unlink(tmp_file))

  # Pass the same file multiple times
  res <- .resolve.files(c(tmp_file, tmp_file, tmp_file))
  expect_equal(length(res), 1)
})

test_that(".resolve.files throws expected errors on empty or non-existent inputs", {
  # Non-existent path
  expect_error(
    .resolve.files("non_existent_folder_xyz123"),
    "No existing files or directories matched"
  )

  # Existing empty directory with pattern filter
  tmp_dir <- file.path(tempdir(), "empty_dir")
  dir.create(tmp_dir, showWarnings = FALSE)
  on.exit(unlink(tmp_dir, recursive = TRUE))

  expect_error(
    .resolve.files(tmp_dir, file.pattern = "*.nonexistent_ext"),
    "No valid files were found matching the specified criteria"
  )
})
