-- CONTRATO DE RETORNO (para o host em Rust):
--  Sucesso ADD: { ok = true,  action = "store",  value = "<valor_para_armazenar>" }
--  Sucesso GET: { ok = true,  action = "return", value = "<valor_para_imprimir>" }
--  Erro:        { ok = false, kind   = "<TipoDeErro>", message = "<explicação>" }
--
-- Rotas por prefixo de chave:
--   - "cpf_*"  → valida CPF no ADD, formata no GET
--   - "data_*" → valida data ISO (aaaa-mm-dd) no ADD, formata dd/mm/aaaa no GET
--   - default  → aceita e retorna como texto

local Modulo = {}

-- =========================
-- Utilidades de strings
-- =========================

--- Remove todos os caracteres que não são dígitos
local function apenas_digitos(s)
  return (s or ""):gsub("%D", "")
end

-- =========================
-- CPF: validação e formatação
-- =========================

--- Verifica se todos os dígitos do CPF são iguais (ex.: 00000000000)
local function cpf_todos_iguais(cpf)
  -- compara a string com o primeiro dígito repetido N vezes
  local primeiro = cpf:sub(1, 1)
  return cpf == primeiro:rep(#cpf)
end

--- Calcula um dígito verificador do CPF
local function cpf_calcular_digito(digitos, multiplicador_inicial)
  local soma = 0
  local peso = multiplicador_inicial
  for i = 1, #digitos do
    soma = soma + tonumber(digitos:sub(i, i)) * peso
    peso = peso - 1
  end
  local resto = soma % 11
  if resto < 2 then return 0 else return 11 - resto end
end

--- Verifica se um CPF (apenas dígitos) é válido
local function cpf_valido(cpf)
  cpf = apenas_digitos(cpf)
  if #cpf ~= 11 then return false end
  if cpf_todos_iguais(cpf) then return false end

  local dv1 = cpf_calcular_digito(cpf:sub(1, 9), 10)
  local dv2 = cpf_calcular_digito(cpf:sub(1, 10), 11)

  return dv1 == tonumber(cpf:sub(10, 10))
     and dv2 == tonumber(cpf:sub(11, 11))
end

--- Formata um CPF (apenas dígitos) como 000.000.000-00
local function cpf_formatar(cpf)
  cpf = apenas_digitos(cpf)
  return string.format("%s.%s.%s-%s",
    cpf:sub(1,3), cpf:sub(4,6), cpf:sub(7,9), cpf:sub(10,11))
end

-- =========================
-- Datas: validação e formatação
-- =========================

--- Retorna true se o ano for bissexto
local function eh_bissexto(ano)
  ano = tonumber(ano)
  return (ano % 400 == 0) or ((ano % 4 == 0) and (ano % 100 ~= 0))
end

--- Valida uma data no formato ISO simples: aaaa-mm-dd
local function data_iso_valida(iso)
  local a, m, d = iso:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)$")
  if not (a and m and d) then return false end

  a, m, d = tonumber(a), tonumber(m), tonumber(d)
  if m < 1 or m > 12 then return false end

  local dias_por_mes = {
    31, (eh_bissexto(a) and 29 or 28), 31, 30, 31, 30,
    31, 31, 30, 31, 30, 31
  }
  if d < 1 or d > dias_por_mes[m] then return false end

  return true
end

--- Converte ISO aaaa-mm-dd para dd/mm/aaaa
local function data_formatar_br(iso)
  local a, m, d = iso:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)$")
  return string.format("%02d/%02d/%04d", tonumber(d), tonumber(m), tonumber(a))
end

-- =========================
-- Handlers: CPF
-- =========================

--- ADD para chaves "cpf_*": valida e normaliza (somente dígitos)
local function tratar_add_cpf(chave, valor)
  -- 1) Exigir exatamente 11 dígitos na entrada original (sem pontos/traços)
if not tostring(valor):match("^%d%d%d%d%d%d%d%d%d%d%d$") then
  return { ok = false, kind = "InvalidCPF",
           message = "Entrada deve conter exatamente 11 dígitos, sem pontuação." }
end

  -- 2) Agora pode validar DV normalmente
  local so_digitos = valor -- já sabemos que é só dígito
  if not cpf_valido(so_digitos) then
    return { ok = false, kind = "InvalidCPF",
             message = "Dígitos verificadores do CPF são inválidos." }
  end
  -- Armazenamos o CPF "limpo" (somente dígitos)
  return { ok = true, action = "store", value = so_digitos }
end

--- GET para chaves "cpf_*": formata como 000.000.000-00
local function tratar_get_cpf(chave, valor_bruto)
  if not valor_bruto or valor_bruto == "" then
    return { ok = false, kind = "NotFound", message = "Chave não encontrada." }
  end
  return { ok = true, action = "return", value = cpf_formatar(valor_bruto) }
end

-- =========================
-- Handlers: Data
-- =========================

--- ADD para chaves "data_*": valida ISO (aaaa-mm-dd)
local function tratar_add_data(chave, valor)
  if not data_iso_valida(valor) then
    return { ok = false, kind = "InvalidDate",
             message = "Data inválida. Use o formato ISO: aaaa-mm-dd." }
  end
  -- Armazenamos no próprio formato ISO
  return { ok = true, action = "store", value = valor }
end

--- GET para chaves "data_*": retorna dd/mm/aaaa
local function tratar_get_data(chave, valor_bruto)
  if not valor_bruto or valor_bruto == "" then
    return { ok = false, kind = "NotFound", message = "Chave não encontrada." }
  end
  if not data_iso_valida(valor_bruto) then
    return { ok = false, kind = "InvalidDate",
             message = "A data armazenada está inválida." }
  end
  return { ok = true, action = "return", value = data_formatar_br(valor_bruto) }
end

-- =========================
-- Handlers: Default (sem extensão)
-- =========================

--- ADD padrão: aceita e armazena como texto
local function tratar_add_padrao(chave, valor)
  return { ok = true, action = "store", value = tostring(valor or "") }
end

--- GET padrão: retorna o que estiver armazenado (como texto)
local function tratar_get_padrao(chave, valor_bruto)
  if not valor_bruto then
    return { ok = false, kind = "NotFound", message = "Chave não encontrada." }
  end
  return { ok = true, action = "return", value = tostring(valor_bruto) }
end

-- =========================
-- API pública chamada pelo host (Rust)
-- =========================

--- Decide qual extensão tratar no ADD, com base no prefixo da chave
function Modulo.handle_add(chave, valor)
  if chave:match("^cpf_") then
    return tratar_add_cpf(chave, valor)
  elseif chave:match("^data_") then
    return tratar_add_data(chave, valor)
  else
    return tratar_add_padrao(chave, valor)
  end
end

--- Decide qual extensão tratar no GET, com base no prefixo da chave
function Modulo.handle_get(chave, valor_bruto)
  if chave:match("^cpf_") then
    return tratar_get_cpf(chave, valor_bruto)
  elseif chave:match("^data_") then
    return tratar_get_data(chave, valor_bruto)
  else
    return tratar_get_padrao(chave, valor_bruto)
  end
end

return Modulo
