--[[
  Sistema de extensões em Lua
  - Retorno SEMPRE é uma tabela: { ok = bool, value = <string|nil>, error = <string|nil> }
  - Acesso ao banco: objeto global STORE (UserData com métodos get/set)
--]]

local function ok(value)
  return { ok = true, value = value, error = nil }
end

local function err(msg)
  return { ok = false, value = nil, error = msg or "erro não especificado" }
end

----------------------------------------------------------------------
-- Utilitários: CPF
----------------------------------------------------------------------

local function cpf_all_equal(s)
  -- rejeita cpfs como 00000000000, 11111111111, etc.
  return s:match("^(%d)%1+$") ~= nil
end

local function cpf_digits(s)
  return s:match("^%d%d%d%d%d%d%d%d%d%d%d$") ~= nil
end

local function cpf_calc_digit(s, len)
  local sum = 0
  local weight = (len == 9) and 10 or 11
  for i = 1, len do
    local n = tonumber(s:sub(i, i))
    sum = sum + n * weight
    weight = weight - 1
  end
  local rem = sum % 11
  if rem < 2 then return 0 else return 11 - rem end
end

local function cpf_is_valid(raw)
  if (not raw) or (type(raw) ~= "string") then return false end
  if not cpf_digits(raw) then return false end
  if cpf_all_equal(raw) then return false end

  local d10 = cpf_calc_digit(raw, 9)
  if d10 ~= tonumber(raw:sub(10,10)) then return false end

  local d11 = cpf_calc_digit(raw, 10)
  if d11 ~= tonumber(raw:sub(11,11)) then return false end

  return true
end

local function cpf_format(raw)
  -- assume 11 dígitos válidos
  return string.format("%s.%s.%s-%s",
    raw:sub(1,3), raw:sub(4,6), raw:sub(7,9), raw:sub(10,11))
end

----------------------------------------------------------------------
-- Utilitários: Data ISO8601 (YYYY-MM-DD)
----------------------------------------------------------------------

local function is_leap(y)
  y = tonumber(y)
  if (y % 400 == 0) then return true end
  if (y % 100 == 0) then return false end
  return (y % 4 == 0)
end

local function valid_date_iso(date)
  if type(date) ~= "string" then return false end
  local y, m, d = date:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)$")
  if not y then return false end
  y, m, d = tonumber(y), tonumber(m), tonumber(d)
  if m < 1 or m > 12 then return false end

  local mdays = {31, (is_leap(y) and 29 or 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
  local maxd = mdays[m]
  if d < 1 or d > maxd then return false end
  return true
end

local function br_date(date) -- YYYY-MM-DD -> DD/MM/YYYY
  local y, m, d = date:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)$")
  return string.format("%s/%s/%s", d, m, y)
end

local function ext_add(key, value, store)
  if type(key) ~= "string" or key == "" then
    return err("chave inválida")
  end
  if type(value) ~= "string" or value == "" then
    return err("valor inválido")
  end

  -- Regra CPF
  if key:match("^cpf_") then
    if not cpf_is_valid(value) then
      return err("CPF inválido: use 11 dígitos sem formatação")
    end
    -- Armazena bruto (11 dígitos)
    store:set(key, value)
    return ok("inserido (cpf válido)")
  end

  -- Regra Data ISO
  if key:match("^data_") then
    if not valid_date_iso(value) then
      return err("Data inválida: use ISO8601 (YYYY-MM-DD)")
    end
    store:set(key, value)
    return ok("inserido (data válida)")
  end

  -- Default: armazena como veio
  store:set(key, value)
  return ok("inserido")
end

local function ext_get(key, store)
  if type(key) ~= "string" or key == "" then
    return err("chave inválida")
  end
  local v = store:get(key)
  if v == nil then
    return err("chave inexistente")
  end

  if key:match("^cpf_") then
    if not cpf_digits(v) or not cpf_is_valid(v) then
      return err("CPF armazenado inválido")
    end
    return ok(cpf_format(v))
  end

  if key:match("^data_") then
    if not valid_date_iso(v) then
      return err("data armazenada inválida")
    end
    return ok(br_date(v))
  end

  -- Default: retorna como está
  return ok(v)
end

----------------------------------------------------------------------
-- Rust chama
----------------------------------------------------------------------

function dispatch(command, key, value, store)
  command = tostring(command or ""):upper()
  if command == "ADD" then
    return ext_add(key, value, store)
  elseif command == "GET" then
    return ext_get(key, store)
  else
    return err("comando desconhecido: " .. tostring(command))
  end
end
