#' Run KEGG microbe-EC-reaction search workflow
#'
#' Import a microbial EC annotation table, normalize EC identifiers, extract
#' species names from taxonomy strings, query KEGG for EC-linked reactions, and
#' append reactants, products, and compound formulae, and optionally write Excel outputs.
#'
#' Supported input formats are `.csv`, `.tsv`, `.txt`, `.xlsx`, and `.xls`.
#'
#' @param input_file Path to input annotation table.
#' @param ec_col Column containing EC numbers. Default is `"EC_Number"`.
#' @param taxonomy_col Column containing taxonomy strings. Default is `"Taxonomy"`.
#' @param output_dir Output directory. If `NULL` (default), no files are written.
#' @param output_file Output Excel filename. Default is
#'   `"microbe_ec_kegg_reactions.xlsx"`.
#' @param sleep_sec Delay between KEGG requests in seconds. Default is `0.35`.
#' @param verbose Whether to print progress. Default is `TRUE`.
#'
#' @return A named list containing:
#' \describe{
#'   \item{results}{full result table with EC, microbe, reaction, compounds, and formulae}
#'   \item{ec_to_reaction}{EC-to-reaction mapping table}
#'   \item{reaction_details}{reaction detail table}
#'   \item{compound_table}{compound formula table}
#' }
#'
#' @examples
#' \donttest{
#' toy <- data.frame(
#'   EC_Number = "1.1.1.1",
#'   Taxonomy = "k__Bacteria;p__Proteobacteria;g__Escherichia;s__Escherichia_coli"
#' )
#' input_file <- tempfile(fileext = ".csv")
#' utils::write.csv(toy, input_file, row.names = FALSE)
#' res <- try(
#'   reft_kegg_microbe_run(input_file, output_dir = tempdir(), sleep_sec = 0,
#'                         verbose = FALSE),
#'   silent = TRUE
#' )
#' if (!inherits(res, "try-error")) head(res$results)
#' }
#'
#' @export
reft_kegg_microbe_run <- function(
  input_file,
  ec_col = "EC_Number",
  taxonomy_col = "Taxonomy",
  output_dir = NULL,
  output_file = "microbe_ec_kegg_reactions.xlsx",
  sleep_sec = 0.35,
  verbose = TRUE
) {
  if (!file.exists(input_file)) {
    stop("input_file does not exist: ", input_file, call. = FALSE)
  }

  dat <- reft_read_tabular(input_file)
  check_required_cols(dat, c(ec_col, taxonomy_col))

  if (verbose) {
    cat("Read", nrow(dat), "rows and", ncol(dat), "columns.\n")
    cat("Column names:\n")
    print(colnames(dat))
  }

  dat2 <- dat %>%
    dplyr::mutate(
      EC_Number = reft_kegg_normalize_ec(.data[[ec_col]]),
      Taxonomy = as.character(.data[[taxonomy_col]]),
      Genus_raw = purrr::map_chr(.data$Taxonomy, reft_extract_rank, rank_prefix = "g__"),
      Species_raw = purrr::map_chr(.data$Taxonomy, reft_extract_rank, rank_prefix = "s__"),
      Genus = reft_clean_taxon_name(.data$Genus_raw),
      Species = reft_clean_taxon_name(.data$Species_raw),
      Microbe = dplyr::case_when(
        !is.na(.data$Species) & .data$Species != "" ~ .data$Species,
        !is.na(.data$Genus) & .data$Genus != "" ~ .data$Genus,
        TRUE ~ .data$Taxonomy
      )
    ) %>%
    dplyr::filter(!is.na(.data$EC_Number), .data$EC_Number != "")

  unique_ec <- dat2 %>%
    dplyr::distinct(.data$EC_Number) %>%
    dplyr::pull(.data$EC_Number)

  if (verbose) {
    cat("Unique EC count:", length(unique_ec), "\n")
  }

  ec_to_reaction <- purrr::map_dfr(unique_ec, function(ec) {
    rxns <- reft_kegg_get_reactions_by_ec(ec, sleep_sec = sleep_sec)

    if (length(rxns) == 0) {
      tibble::tibble(EC_Number = ec, Reaction_ID = NA_character_)
    } else {
      tibble::tibble(EC_Number = ec, Reaction_ID = rxns)
    }
  })

  unique_rxn <- ec_to_reaction %>%
    dplyr::filter(!is.na(.data$Reaction_ID), .data$Reaction_ID != "") %>%
    dplyr::distinct(.data$Reaction_ID) %>%
    dplyr::pull(.data$Reaction_ID)

  if (verbose) {
    cat("Unique reaction count:", length(unique_rxn), "\n")
  }

  reaction_details <- purrr::map_dfr(
    unique_rxn,
    ~ reft_kegg_get_reaction_details(.x, sleep_sec = sleep_sec)
  )

  all_compounds <- unique(c(
    unlist(lapply(reaction_details$Reactants, reft_extract_compound_ids), use.names = FALSE),
    unlist(lapply(reaction_details$Products, reft_extract_compound_ids), use.names = FALSE)
  ))
  all_compounds <- all_compounds[!is.na(all_compounds) & all_compounds != ""]

  if (verbose) {
    cat("Unique compound count:", length(all_compounds), "\n")
  }

  compound_table <- purrr::map_dfr(
    all_compounds,
    ~ reft_kegg_get_compound_formula(.x, sleep_sec = sleep_sec)
  )

  reaction_details2 <- reaction_details %>%
    dplyr::rowwise() %>%
    dplyr::mutate(
      Reactants_With_Formula = reft_replace_compounds_with_formula(.data$Reactants, compound_table),
      Products_With_Formula = reft_replace_compounds_with_formula(.data$Products, compound_table)
    ) %>%
    dplyr::ungroup()

  final_df <- dat2 %>%
    dplyr::select(dplyr::any_of(c(ec_col, taxonomy_col)), .data$EC_Number, .data$Taxonomy,
                  .data$Genus, .data$Species, .data$Microbe) %>%
    dplyr::left_join(ec_to_reaction, by = "EC_Number") %>%
    dplyr::left_join(reaction_details2, by = "Reaction_ID") %>%
    dplyr::arrange(.data$EC_Number, .data$Microbe, .data$Reaction_ID)

  if (!is.null(output_dir)) {
    if (!is.character(output_dir) || length(output_dir) != 1 ||
        is.na(output_dir) || output_dir == "") {
      stop("output_dir must be NULL or a non-empty character string.", call. = FALSE)
    }
    dir.create(output_dir, showWarnings = FALSE, recursive = TRUE)
    writexl::write_xlsx(
      list(
        results = as.data.frame(final_df),
        ec_to_reaction = as.data.frame(ec_to_reaction),
        reaction_details = as.data.frame(reaction_details2),
        compound_table = as.data.frame(compound_table)
      ),
      path = file.path(output_dir, output_file)
    )

    if (verbose) {
      cat("Output file:", file.path(output_dir, output_file), "\n")
    }
  }

  list(
    results = final_df,
    ec_to_reaction = ec_to_reaction,
    reaction_details = reaction_details2,
    compound_table = compound_table
  )
}

reft_read_tabular <- function(input_file) {
  ext <- tolower(tools::file_ext(input_file))

  if (ext %in% c("xlsx", "xls")) {
    return(as.data.frame(readxl::read_excel(input_file), stringsAsFactors = FALSE))
  }
  if (ext == "csv") {
    return(utils::read.csv(input_file, stringsAsFactors = FALSE, check.names = FALSE))
  }
  if (ext %in% c("tsv", "txt")) {
    return(utils::read.delim(input_file, stringsAsFactors = FALSE, check.names = FALSE))
  }

  stop("Unsupported file format: ", ext, ". Please use csv, tsv, txt, xlsx, or xls.", call. = FALSE)
}

reft_extract_rank <- function(taxonomy, rank_prefix = "s__") {
  if (is.na(taxonomy) || taxonomy == "") return(NA_character_)
  parts <- stringr::str_split(as.character(taxonomy), ";", simplify = FALSE)[[1]]
  parts <- stringr::str_trim(parts)
  hit <- parts[stringr::str_detect(parts, paste0("^", stringr::fixed(rank_prefix)))]
  if (length(hit) == 0) return(NA_character_)
  stringr::str_remove(hit[1], paste0("^", rank_prefix))
}

reft_clean_taxon_name <- function(x) {
  ifelse(
    is.na(x),
    NA_character_,
    x |>
      stringr::str_replace_all("_", " ") |>
      stringr::str_squish()
  )
}

reft_kegg_normalize_ec <- function(ec) {
  ec <- as.character(ec)
  ec <- stringr::str_trim(ec)
  ec <- stringr::str_remove(ec, stringr::regex("^ec:", ignore_case = TRUE))
  ec <- stringr::str_remove(ec, stringr::regex("^EC:", ignore_case = TRUE))
  ec <- stringr::str_squish(ec)
  ec[is.na(ec) | ec %in% c("", "NA", "N/A", "NULL", "--", "na", "null")] <- NA_character_
  ec
}

reft_kegg_get_text <- function(url, sleep_sec = 0.35) {
  Sys.sleep(sleep_sec)
  tryCatch({
    txt <- readLines(url, warn = FALSE, encoding = "UTF-8")
    if (length(txt) == 0) return(NA_character_)
    paste(txt, collapse = "\n")
  }, error = function(e) {
    message("Request failed: ", url)
    message("Error: ", e$message)
    NA_character_
  })
}

reft_kegg_get_reactions_by_ec <- function(ec_number, sleep_sec = 0.35) {
  if (is.na(ec_number) || ec_number == "") return(character(0))
  ec_number <- reft_kegg_normalize_ec(ec_number)
  url <- paste0("https://rest.kegg.jp/link/reaction/ec:", ec_number)
  txt <- reft_kegg_get_text(url, sleep_sec = sleep_sec)
  if (is.na(txt) || txt == "") return(character(0))
  lines <- stringr::str_split(txt, "\n", simplify = FALSE)[[1]]
  lines <- lines[lines != ""]
  if (length(lines) == 0) return(character(0))
  rxn_ids <- stringr::str_match(lines, "rn:(R\\d{5})")[, 2]
  unique(stats::na.omit(rxn_ids))
}

reft_kegg_extract_field_block <- function(text, field_name) {
  if (is.na(text) || text == "") return(NA_character_)
  lines <- stringr::str_split(text, "\n", simplify = FALSE)[[1]]
  if (length(lines) == 0) return(NA_character_)
  start_idx <- which(stringr::str_detect(lines, paste0("^", field_name, "\\s+")))
  if (length(start_idx) == 0) return(NA_character_)
  start_idx <- start_idx[1]
  out <- c()
  first_line <- lines[start_idx]
  out <- c(out, stringr::str_trim(stringr::str_replace(first_line, paste0("^", field_name, "\\s+"), "")))
  i <- start_idx + 1
  while (i <= length(lines) && stringr::str_detect(lines[i], "^\\s{12}\\S")) {
    out <- c(out, stringr::str_trim(lines[i]))
    i <- i + 1
  }
  out <- out[out != ""]
  if (length(out) == 0) return(NA_character_)
  stringr::str_squish(paste(out, collapse = " "))
}

reft_parse_equation_side_to_vector <- function(side_text) {
  if (is.na(side_text) || side_text == "") return(character(0))
  parts <- side_text %>%
    stringr::str_split("\\s+\\+\\s+", simplify = FALSE) %>%
    .[[1]] %>%
    stringr::str_squish()
  parts[parts != ""]
}

reft_parse_equation <- function(equation) {
  if (is.na(equation) || equation == "") {
    return(list(reactants = NA_character_, products = NA_character_))
  }
  parts <- NULL
  if (stringr::str_detect(equation, "<=>")) {
    parts <- stringr::str_split(equation, "\\s*<=>\\s*", simplify = FALSE)[[1]]
  } else if (stringr::str_detect(equation, "=>")) {
    parts <- stringr::str_split(equation, "\\s*=>\\s*", simplify = FALSE)[[1]]
  } else if (stringr::str_detect(equation, "<=")) {
    parts <- stringr::str_split(equation, "\\s*<=\\s*", simplify = FALSE)[[1]]
  }
  if (is.null(parts) || length(parts) < 2) {
    return(list(reactants = NA_character_, products = NA_character_))
  }
  reactant_vec <- reft_parse_equation_side_to_vector(parts[1])
  product_vec <- reft_parse_equation_side_to_vector(parts[2])
  list(
    reactants = if (length(reactant_vec) > 0) paste(reactant_vec, collapse = "; ") else NA_character_,
    products = if (length(product_vec) > 0) paste(product_vec, collapse = "; ") else NA_character_
  )
}

reft_kegg_get_reaction_details <- function(rn_id, sleep_sec = 0.35) {
  if (is.na(rn_id) || rn_id == "") {
    return(tibble::tibble(
      Reaction_ID = NA_character_,
      Reaction_Name = NA_character_,
      Definition = NA_character_,
      Equation = NA_character_,
      Reactants = NA_character_,
      Products = NA_character_
    ))
  }
  url <- paste0("https://rest.kegg.jp/get/rn:", rn_id)
  txt <- reft_kegg_get_text(url, sleep_sec = sleep_sec)
  if (is.na(txt) || txt == "") {
    return(tibble::tibble(
      Reaction_ID = rn_id,
      Reaction_Name = NA_character_,
      Definition = NA_character_,
      Equation = NA_character_,
      Reactants = NA_character_,
      Products = NA_character_
    ))
  }
  equation <- reft_kegg_extract_field_block(txt, "EQUATION")
  eq_parsed <- reft_parse_equation(equation)
  tibble::tibble(
    Reaction_ID = rn_id,
    Reaction_Name = reft_kegg_extract_field_block(txt, "NAME"),
    Definition = reft_kegg_extract_field_block(txt, "DEFINITION"),
    Equation = equation,
    Reactants = eq_parsed$reactants,
    Products = eq_parsed$products
  )
}

reft_extract_compound_ids <- function(text) {
  if (is.na(text) || text == "") return(character(0))
  ids <- stringr::str_extract_all(text, "C\\d{5}")[[1]]
  unique(ids)
}

reft_kegg_get_compound_formula <- function(compound_id, sleep_sec = 0.35) {
  if (is.na(compound_id) || compound_id == "") {
    return(tibble::tibble(
      Compound_ID = NA_character_,
      Compound_Name = NA_character_,
      Formula = NA_character_
    ))
  }
  compound_id <- stringr::str_extract(compound_id, "C\\d{5}")
  if (is.na(compound_id)) {
    return(tibble::tibble(
      Compound_ID = NA_character_,
      Compound_Name = NA_character_,
      Formula = NA_character_
    ))
  }
  url <- paste0("https://rest.kegg.jp/get/cpd:", compound_id)
  txt <- reft_kegg_get_text(url, sleep_sec = sleep_sec)
  if (is.na(txt) || txt == "") {
    return(tibble::tibble(
      Compound_ID = compound_id,
      Compound_Name = NA_character_,
      Formula = NA_character_
    ))
  }
  tibble::tibble(
    Compound_ID = compound_id,
    Compound_Name = reft_kegg_extract_field_block(txt, "NAME"),
    Formula = reft_kegg_extract_field_block(txt, "FORMULA")
  )
}

reft_replace_compounds_with_formula <- function(side_text, formula_map) {
  if (is.na(side_text) || side_text == "") return(NA_character_)
  parts <- reft_parse_equation_side_to_vector(side_text)
  if (length(parts) == 0) return(NA_character_)
  parts2 <- vapply(parts, function(x) {
    cid <- stringr::str_extract(x, "C\\d{5}")
    if (is.na(cid)) return(x)
    hit <- formula_map %>% dplyr::filter(.data$Compound_ID == cid)
    formula <- if (nrow(hit) > 0) hit$Formula[1] else NA_character_
    name <- if (nrow(hit) > 0) hit$Compound_Name[1] else NA_character_
    suffix <- paste0(
      " [",
      ifelse(is.na(formula) || formula == "", "NA", formula),
      ifelse(is.na(name) || name == "", "", paste0(" | ", name)),
      "]"
    )
    paste0(x, suffix)
  }, character(1))
  paste(parts2, collapse = "; ")
}
