library(dplyr)
library(readxl)
library(stringr)
library(purrr)
library(arrow)

# funcao para obter linhas com mudancas de codigo em cada revisao NCM
filtra_mudancas <- function(x, padrao) {
  x |>
    select(matches(padrao)) |>
    filter(
      # seleciona as linhas que tem algum valor diferente de NA, excluindo
      # a primeira coluna da comparacao
      if_any(-1, ~ !is.na(.))
    ) |>
    # cria uma coluna preenchida com o primeiro valor nao NA da
    # direita para a esquerda
    mutate(!!padrao := do.call(coalesce, rev(as.list(across(-1))))) |>
    select(
      ncm_original = 1,
      last_col()
    ) |>
    distinct()
}

amostra_valores <- function(x, n = 5) {
  x <- unique(x)
  x <- x[!is.na(x)]

  if (length(x) == 0) {
    return("sem amostra")
  }

  paste(head(sort(x), n), collapse = ", ")
}

sinaliza_validacao <- function(...) {
  warning(sprintf(...), call. = FALSE)
}

interrompe_execucao <- function(...) {
  stop(sprintf(...), call. = FALSE)
}

normaliza_ncm <- function(x, validar = FALSE, contexto = "NCM") {
  x <- as.character(x)
  x <- str_trim(x)
  x <- str_remove_all(x, "\\.")

  if (validar) {
    invalidos <- x[!is.na(x) & !str_detect(x, "^\\d{8}$")]
    invalidos <- unique(invalidos)

    if (length(invalidos) > 0) {
      sinaliza_validacao(
        "Foram encontrados %s NCM(s) com formato invalido em %s. Amostra: %s",
        length(invalidos),
        contexto,
        amostra_valores(invalidos)
      )
    }
  }

  x
}

# carrega caminhos para dados
general_dir <- Sys.getenv("general")
if (!nzchar(general_dir)) {
  interrompe_execucao("A variavel de ambiente general esta vazia.")
}

if (!dir.exists(general_dir)) {
  interrompe_execucao(
    "O diretorio indicado pela variavel general nao existe: %s",
    general_dir
  )
}

path_tec <- Sys.getenv("tec")
if (!nzchar(path_tec)) {
  interrompe_execucao("A variavel de ambiente tec esta vazia.")
}

if (!file.exists(path_tec)) {
  interrompe_execucao(
    "O arquivo TEC indicado pela variavel tec nao foi encontrado: %s",
    path_tec
  )
}

# caminho correlacao normal
path_ncm <- file.path(
  general_dir,
  "Bases",
  "correlacoes",
  "mercosul",
  "output",
  "correlacao_completa_ncm.xlsx"
)

# caminho correlacao expandida
path_ncm_detalhada <- file.path(
  general_dir,
  "Bases",
  "correlacoes",
  "mercosul",
  "output",
  "correlacao_completa_ncm_detalhada.xlsx"
)

# Na pasta dados_gerados, "snapshot" identifica a ultima versao gerada
# e salva para servir de referencia nas comparacoes com os arquivos atuais.
path_ncms_originais_snapshot <- "dados_gerados/ncms_originais.rds"
path_dataset_ncm_snapshot <- "dados_gerados/dataset_ncm.parquet"

arquivos_necessarios <- c(
  correlacao_ncm = path_ncm,
  correlacao_ncm_detalhada = path_ncm_detalhada,
  tec = path_tec,
  ncms_originais_snapshot = path_ncms_originais_snapshot,
  dataset_ncm_snapshot = path_dataset_ncm_snapshot
)

arquivos_faltantes <- arquivos_necessarios[!file.exists(arquivos_necessarios)]
if (length(arquivos_faltantes) > 0) {
  interrompe_execucao(
    "Os seguintes arquivos obrigatorios nao foram encontrados: %s",
    paste(
      sprintf("%s=%s", names(arquivos_faltantes), arquivos_faltantes),
      collapse = "; "
    )
  )
}

message("Ambiente configurado e caminhos obrigatorios validados.")

# leitura dos arquivos obrigatorios
correlacao_ncm_detalhada <- read_xlsx(path_ncm_detalhada, guess_max = 1e6)
correlacao_completa_ncm_raw <- read_xlsx(path_ncm, guess_max = 1e6)
tec <- read_xlsx(path_tec, guess_max = 1e6)
ncms_originais_snapshot <- readRDS(path_ncms_originais_snapshot)
dataset_ncm_snapshot <- read_parquet(path_dataset_ncm_snapshot)

message("Dados carregados.")

# cria lista de nomes para usar como input em filtra_mudancas
revisoes_ncm <- names(correlacao_ncm_detalhada) |>
  str_extract("NCM_\\d{4}") |>
  discard(is.na) |>
  unique()

if (length(revisoes_ncm) == 0) {
  interrompe_execucao(
    "Nenhuma coluna no formato NCM_#### foi encontrada na correlacao detalhada."
  )
}

anos_revisoes <- as.integer(str_extract(revisoes_ncm, "\\d{4}"))
indice_ultima_revisao <- which.max(anos_revisoes)
coluna_ultima_revisao <- revisoes_ncm[[indice_ultima_revisao]]
nome_ultima_revisao <- str_replace(coluna_ultima_revisao, "_", " ")

# obtem lista vigente com os NCMs originais das revisoes
ncms_originais <- revisoes_ncm |>
  map(~ filtra_mudancas(correlacao_ncm_detalhada, padrao = .x))

names(ncms_originais) <- str_replace(revisoes_ncm, "_", " ")

if (!nome_ultima_revisao %in% names(ncms_originais)) {
  interrompe_execucao(
    "A revisao mais recente %s nao foi gerada a partir da correlacao detalhada.",
    nome_ultima_revisao
  )
}

if (
  !is.list(ncms_originais_snapshot) || is.null(names(ncms_originais_snapshot))
) {
  interrompe_execucao(
    "O arquivo snapshot de ncms_originais esta invalido ou sem nomes de revisao."
  )
}

if (indice_ultima_revisao > length(ncms_originais_snapshot)) {
  interrompe_execucao(
    "O snapshot de ncms_originais nao possui o indice %s correspondente a %s.",
    indice_ultima_revisao,
    nome_ultima_revisao
  )
}

if (!nome_ultima_revisao %in% names(ncms_originais_snapshot)) {
  interrompe_execucao(
    "O snapshot de ncms_originais nao possui a revisao %s.",
    nome_ultima_revisao
  )
}

if (
  !identical(
    names(ncms_originais_snapshot)[[indice_ultima_revisao]],
    nome_ultima_revisao
  )
) {
  interrompe_execucao(
    paste(
      "O snapshot de ncms_originais tem %s no indice %s,",
      "mas o esperado para a revisao atual era %s."
    ),
    names(ncms_originais_snapshot)[[indice_ultima_revisao]],
    indice_ultima_revisao,
    nome_ultima_revisao
  )
}

# separa a revisao mais recente do arquivo atual e do snapshot correspondente
ultima_revisao_atual <- ncms_originais[[nome_ultima_revisao]]
ultima_revisao_snapshot <- ncms_originais_snapshot[[nome_ultima_revisao]]

if (
  !all(
    c("ncm_original", coluna_ultima_revisao) %in% names(ultima_revisao_atual)
  )
) {
  interrompe_execucao(
    "A revisao atual %s nao contem as colunas esperadas ncm_original e %s.",
    nome_ultima_revisao,
    coluna_ultima_revisao
  )
}

if (
  !all(
    c("ncm_original", coluna_ultima_revisao) %in% names(ultima_revisao_snapshot)
  )
) {
  interrompe_execucao(
    "O snapshot de %s nao contem as colunas esperadas ncm_original e %s.",
    nome_ultima_revisao,
    coluna_ultima_revisao
  )
}

ultima_revisao_atual <- ultima_revisao_atual |>
  mutate(
    ncm_original = normaliza_ncm(
      ncm_original,
      validar = TRUE,
      contexto = sprintf(
        "%s na coluna ncm_original do arquivo atual",
        nome_ultima_revisao
      )
    ),
    !!coluna_ultima_revisao := normaliza_ncm(
      .data[[coluna_ultima_revisao]],
      validar = TRUE,
      contexto = sprintf(
        "%s na coluna %s do arquivo atual",
        nome_ultima_revisao,
        coluna_ultima_revisao
      )
    )
  )

ultima_revisao_snapshot <- ultima_revisao_snapshot |>
  mutate(
    ncm_original = normaliza_ncm(
      ncm_original,
      validar = TRUE,
      contexto = sprintf(
        "%s na coluna ncm_original do snapshot",
        nome_ultima_revisao
      )
    ),
    !!coluna_ultima_revisao := normaliza_ncm(
      .data[[coluna_ultima_revisao]],
      validar = TRUE,
      contexto = sprintf(
        "%s na coluna %s do snapshot",
        nome_ultima_revisao,
        coluna_ultima_revisao
      )
    )
  )

message(sprintf("Revisao mais recente identificada: %s.", nome_ultima_revisao))

# compara a coluna ncm_original do arquivo atual com a do snapshot
# indica codigos que nao estavam na revisao original e passaram a aparecer agora.
# Isso e importante porque alguns codigos podem ter sido alterados agora ou podem
# ter sido esquecidos na geracao anterior.
ncm_original_so_atual <- setdiff(
  unique(ultima_revisao_atual$ncm_original),
  unique(ultima_revisao_snapshot$ncm_original)
)
if (length(ncm_original_so_atual) > 0) {
  sinaliza_validacao(
    paste(
      "A coluna ncm_original da revisao atual %s contem %s codigo(s)",
      "que nao aparecem no snapshot. Amostra: %s"
    ),
    nome_ultima_revisao,
    length(ncm_original_so_atual),
    amostra_valores(ncm_original_so_atual)
  )
}

ncm_original_so_snapshot <- setdiff(
  unique(ultima_revisao_snapshot$ncm_original),
  unique(ultima_revisao_atual$ncm_original)
)
if (length(ncm_original_so_snapshot) > 0) {
  sinaliza_validacao(
    paste(
      "A coluna ncm_original do snapshot de %s contem %s codigo(s)",
      "que nao aparecem no arquivo atual. Amostra: %s"
    ),
    nome_ultima_revisao,
    length(ncm_original_so_snapshot),
    amostra_valores(ncm_original_so_snapshot)
  )
}

# compara a coluna de mudancas dentro da revisao. Indica codigos que
# apareceram agora em relacao ao snapshot. Aqui vale a pena conferir se
# todos os codigos que aparecem foram de fato criados. Tambem vale conferir se
# todos os codigos criados (olhar nas resolucoes) aparecem nos codigos seguintes.
codigos_revisao_so_atual <- setdiff(
  unique(ultima_revisao_atual[[coluna_ultima_revisao]]),
  unique(ultima_revisao_snapshot[[coluna_ultima_revisao]])
)
if (length(codigos_revisao_so_atual) > 0) {
  sinaliza_validacao(
    paste(
      "A coluna %s da revisao atual contem %s codigo(s)",
      "que nao aparecem no snapshot. Amostra: %s"
    ),
    coluna_ultima_revisao,
    length(codigos_revisao_so_atual),
    amostra_valores(codigos_revisao_so_atual)
  )
}

# mostra códigos que saíram do arquivo atual em relação ao snapshot.
codigos_revisao_so_snapshot <- setdiff(
  unique(ultima_revisao_snapshot[[coluna_ultima_revisao]]),
  unique(ultima_revisao_atual[[coluna_ultima_revisao]])
)
if (length(codigos_revisao_so_snapshot) > 0) {
  sinaliza_validacao(
    paste(
      "A coluna %s do snapshot contem %s codigo(s)",
      "que nao aparecem na revisao atual. Amostra: %s"
    ),
    coluna_ultima_revisao,
    length(codigos_revisao_so_snapshot),
    amostra_valores(codigos_revisao_so_snapshot)
  )
}

message(
  sprintf(
    "Comparacao da revisao %s com o snapshot de ncms_originais concluida.",
    nome_ultima_revisao
  )
)

# compara codigos NCM da tabela de correlacao NCM atual
# e verifica se todos estao presentes na TEC vigente.
correlacao_completa_ncm <- correlacao_completa_ncm_raw |>
  rename_with(~ str_to_upper(.x)) |>
  tidyr::drop_na()

linhas_duplicadas <- nrow(correlacao_completa_ncm) -
  nrow(distinct(correlacao_completa_ncm))
if (linhas_duplicadas > 0) {
  sinaliza_validacao(
    paste(
      "O dataset NCM atual contem %s linha(s) duplicada(s);",
      "elas serao removidas com distinct()."
    ),
    linhas_duplicadas
  )
}

correlacao_completa_ncm <- correlacao_completa_ncm |>
  distinct()

colunas_ncm_dataset <- names(correlacao_completa_ncm) |>
  str_subset("^NCM_\\d{4}$")

if (length(colunas_ncm_dataset) == 0) {
  interrompe_execucao(
    "O dataset NCM atual nao contem colunas no formato NCM_####."
  )
}

if (!coluna_ultima_revisao %in% colunas_ncm_dataset) {
  interrompe_execucao(
    "O dataset NCM atual nao contem a coluna %s correspondente a ultima revisao.",
    coluna_ultima_revisao
  )
}

colunas_com_na <- colunas_ncm_dataset[
  colSums(is.na(correlacao_completa_ncm[colunas_ncm_dataset])) > 0
]

if (length(colunas_com_na) > 0) {
  linhas_com_na <- sum(
    rowSums(is.na(correlacao_completa_ncm[colunas_ncm_dataset])) > 0
  )

  sinaliza_validacao(
    "O dataset NCM atual contem NA em %s linha(s). Colunas afetadas: %s",
    linhas_com_na,
    amostra_valores(colunas_com_na)
  )
}

correlacao_ncm_atual <- correlacao_completa_ncm |>
  rename(ncm = all_of(coluna_ultima_revisao)) |>
  mutate(
    ncm = normaliza_ncm(
      ncm,
      validar = TRUE,
      contexto = sprintf("dataset NCM atual para %s", nome_ultima_revisao)
    )
  )

message("Leitura do dataset NCM atual concluida.")

if (!"ncm" %in% names(tec)) {
  interrompe_execucao("O arquivo TEC nao contem a coluna ncm.")
}

if (!"descricao_tec" %in% names(tec)) {
  interrompe_execucao("O arquivo TEC nao contem a coluna descricao_tec.")
}

if (anyNA(tec$ncm)) {
  sinaliza_validacao("O arquivo TEC contem NA na coluna ncm.")
}

tec <- tec |>
  mutate(
    ncm = normaliza_ncm(
      ncm,
      validar = TRUE,
      contexto = "arquivo TEC"
    )
  )

duplicados_tec <- tec$ncm[duplicated(tec$ncm)] |>
  unique()

if (length(duplicados_tec) > 0) {
  sinaliza_validacao(
    "O arquivo TEC contem %s NCM(s) duplicado(s). Amostra: %s",
    length(duplicados_tec),
    amostra_valores(duplicados_tec)
  )
}

# observa mudancas nos codigos NCM do snapshot da revisao
# e verifica se alguma NCM nao esta mais na TEC vigente.
# Se um codigo aparecer, esse codigo deve ter sido alterado. Conferir.
faltantes_snapshot_tec <- ultima_revisao_snapshot |>
  transmute(
    ncm = .data[[coluna_ultima_revisao]]
  ) |>
  distinct() |>
  left_join(
    tec |>
      select(ncm, descricao_tec),
    by = "ncm"
  ) |>
  filter(is.na(descricao_tec)) |>
  pull(ncm)

if (length(faltantes_snapshot_tec) > 0) {
  sinaliza_validacao(
    "Análise apenas para fins de controle.",
    "A TEC vigente nao cobre %s NCM(s) presentes no snapshot de %s. Amostra: %s",
    length(faltantes_snapshot_tec),
    nome_ultima_revisao,
    amostra_valores(faltantes_snapshot_tec)
  )
}

faltantes_atual_tec <- correlacao_ncm_atual |>
  transmute(ncm) |>
  distinct() |>
  left_join(
    tec |>
      select(ncm, descricao_tec),
    by = "ncm"
  ) |>
  filter(is.na(descricao_tec)) |>
  pull(ncm)

if (length(faltantes_atual_tec) > 0) {
  sinaliza_validacao(
    "Importante!",
    "A TEC vigente nao cobre %s NCM(s) presentes no dataset correlacao ncm completa de %s. Amostra: %s",
    length(faltantes_atual_tec),
    nome_ultima_revisao,
    amostra_valores(faltantes_atual_tec)
  )
}

message("Comparacao com a TEC concluida.")

if (!coluna_ultima_revisao %in% names(dataset_ncm_snapshot)) {
  interrompe_execucao(
    "O snapshot de dataset_ncm nao contem a coluna %s correspondente a ultima revisao.",
    coluna_ultima_revisao
  )
}

correlacao_ncm_snapshot <- dataset_ncm_snapshot |>
  rename(ncm = all_of(coluna_ultima_revisao)) |>
  mutate(
    ncm = normaliza_ncm(
      ncm,
      validar = TRUE,
      contexto = sprintf("snapshot de dataset_ncm para %s", nome_ultima_revisao)
    )
  )

# compara codigos que entraram e sairam no dataset atual com o snapshot.
# Os resultados devem bater com as mudancas observadas na ultima revisao.

# codigos correlacao ncm que entraram
codigos_entraram_dataset_snapshot <- setdiff(
  unique(correlacao_ncm_atual$ncm),
  unique(correlacao_ncm_snapshot$ncm)
)

# codigos correlacao ncm que sairam
codigos_sairam_dataset_snapshot <- setdiff(
  unique(correlacao_ncm_snapshot$ncm),
  unique(correlacao_ncm_atual$ncm)
)

# ncms originais - codigos que entraram
codigos_entraram_revisao_snapshot <- setdiff(
  unique(ultima_revisao_atual[[coluna_ultima_revisao]]),
  unique(ultima_revisao_snapshot[[coluna_ultima_revisao]])
)

# ncms originais - codigos que sairam
codigos_sairam_revisao_snapshot <- setdiff(
  unique(ultima_revisao_snapshot[[coluna_ultima_revisao]]),
  unique(ultima_revisao_atual[[coluna_ultima_revisao]])
)

entradas_so_dataset_atual <- setdiff(
  codigos_entraram_dataset_snapshot,
  codigos_entraram_revisao_snapshot
)
entradas_so_revisao_atual <- setdiff(
  codigos_entraram_revisao_snapshot,
  codigos_entraram_dataset_snapshot
)

if (
  length(entradas_so_dataset_atual) > 0 || length(entradas_so_revisao_atual) > 0
) {
  sinaliza_validacao(
    paste(
      "As entradas de codigos divergem entre o dataset atual e a revisao",
      "comparada com o snapshot de %s.",
      "Somente no dataset atual: %s codigo(s). Amostra: %s.",
      "Somente na revisao atual: %s codigo(s). Amostra: %s."
    ),
    nome_ultima_revisao,
    length(entradas_so_dataset_atual),
    amostra_valores(entradas_so_dataset_atual),
    length(entradas_so_revisao_atual),
    amostra_valores(entradas_so_revisao_atual)
  )
}

saidas_so_dataset_atual <- setdiff(
  # codigos que sairam da correlacao ncm completa snapshot
  codigos_sairam_dataset_snapshot,
  # codigos que sairam da correlacao ncm detalhada snapshot
  codigos_sairam_revisao_snapshot
)
saidas_so_revisao_atual <- setdiff(
  codigos_sairam_revisao_snapshot,
  codigos_sairam_dataset_snapshot
)

if (
  length(saidas_so_dataset_atual) > 0 || length(saidas_so_revisao_atual) > 0
) {
  sinaliza_validacao(
    paste(
      "Análise apenas para fins de controle. É normal ter alguma divergência.",
      "As saidas de codigos divergem entre o dataset correlacao ncm e a correlacao ncm detalhada",
      "de %s.",
      "Saídas presentes em correlacao ncm completa e ausentes em ncm completa detalhada: %s codigo(s). Amostra: %s.",
      "Saídas presentes em correlacao ncm detalhada e ausentes em correlacao ncm completa: %s codigo(s). Amostra: %s."
    ),
    nome_ultima_revisao,
    length(saidas_so_dataset_atual),
    amostra_valores(saidas_so_dataset_atual),
    length(saidas_so_revisao_atual),
    amostra_valores(saidas_so_revisao_atual)
  )
}

message("Comparacao com o snapshot de dataset_ncm concluida.")
message("Validacao concluida.")
