--[[
 route.lua — запрашивает pulse-lets-go для маршрутизации вызова.
 Телефонный балансировщик нагрузки на базе FreeSWITCH + NATS.

 ┌─ Размещение ─────────────────────────────────────────────────────┐
 │ /etc/freeswitch/scripts/route.lua                                │
 │                                                                   │
 │ ┌─ Использование в dialplan ────────────────────────────────────┐│
 │ │ <extension name="route_call">                                 ││
 │ │   <condition field="destination_number" expression="^.*$">    ││
 │ │     <action application="lua" data="route.lua"/>              ││
 │ │   </condition>                                                ││
 │ │ </extension>                                                  ││
 │ └───────────────────────────────────────────────────────────────┘│
 │                                                                   │
 │ ┌─ Переменные канала (опционально) ─────────────────────────────┐│
 │ │ balancer_url    — URL pulse-lets-go (default: localhost:8080) ││
 │ │ ingress_trunk   — ID входящего транка                         ││
 │ └───────────────────────────────────────────────────────────────┘│
 │                                                                   │
 │ ┌─ Требования FreeSWITCH ───────────────────────────────────────┐│
 │ │ mod_curl  — <load module="mod_curl"/> в modules.conf.xml      ││
 │ │ cjson     — входит в стандартную поставку FS Lua              ││
 │ └───────────────────────────────────────────────────────────────┘│
 └───────────────────────────────────────────────────────────────────┘

 Логика:
   1. Извлечь caller_id + destination из переменных сессии
   2. HTTP GET /api/route?caller_id=X&dest=Y&ingress_trunk=Z
   3. Разобрать JSON-ответ
   4. При fallback=true → bridge на fallback gateway (оператор)
   5. При ошибке → кладём трубку с NORMAL_TEMPORARY_FAILURE
   6. Нормальный маршрут → bridge на sip_gateway выбранной ноды
]]--

-- ===================================================================
-- Конфигурация
-- ===================================================================

-- URL балансировщика (переопределяется через переменную канала)
local BALANCER_URL = session:getVariable("balancer_url") or "http://localhost:8080"

-- Таймаут HTTP-запроса в секундах (через параметр curl)
local HTTP_TIMEOUT = tonumber(session:getVariable("route_timeout") or "3")

-- JSON-парсер
local cjson = require("cjson")

-- API FreeSWITCH для вызова mod_curl
local api = freeswitch.API()

-- ===================================================================
-- Вспомогательные функции
-- ===================================================================

-- Логирование с префиксом [route]
local function log(level, msg)
    freeswitch.consoleLog(level, "[route] " .. msg .. "\n")
end

-- Выполнить HTTP GET и вернуть тело ответа (без заголовков).
-- mod_curl API возвращает сырой HTTP-ответ: заголовки + \r?\n\r?\n + тело.
local function http_get(url)
    local cmd = "curl " .. url
    local raw = api:execute("curl", cmd)

    if not raw or raw == "" then
        return nil, "пустой ответ от curl"
    end

    -- Отделяем заголовки от тела
    -- Ищем двойной перенос строки (CRLF или LF)
    local body = raw:match("\r?\n\r?\n(.+)")
    if not body then
        -- Если нет заголовков — весь ответ это тело
        return raw, nil
    end
    return body, nil
end

-- Безопасная отправка трубки с кодом причины
local function safe_hangup(cause)
    if session:ready() then
        session:hangup(cause or "NORMAL_TEMPORARY_FAILURE")
    end
end

-- ===================================================================
-- Основная логика
-- ===================================================================

-- Извлекаем параметры вызова
local caller_id = session:getVariable("caller_id_number") or ""
local dest      = session:getVariable("destination_number") or ""
local ing_trunk = session:getVariable("ingress_trunk") or ""

-- Собираем URL
local query = string.format("caller_id=%s&dest=%s", caller_id, dest)
if ing_trunk ~= "" then
    query = query .. "&ingress_trunk=" .. ing_trunk
end
local url = BALANCER_URL .. "/api/route?" .. query

log("info", "запрос маршрута: " .. url)

-- Запрашиваем маршрут у pulse-lets-go
local body, curl_err = http_get(url)
if not body then
    log("err", "ошибка HTTP: " .. (curl_err or "unknown"))
    safe_hangup()
    return
end

-- Парсим JSON
local ok, route = pcall(cjson.decode, body)
if not ok or type(route) ~= "table" then
    log("err", "ошибка парсинга JSON: " .. tostring(route))
    safe_hangup()
    return
end

-- ── Фолбэк (все ноды unhealthy) ──────────────────────────────────
if route.fallback and route.sip_gateway then
    log("info", string.format("FALLBACK → %s (причина: %s)",
        route.sip_gateway, route.reason or "all_nodes_unhealthy"))

    if route.nodes then
        for _, n in ipairs(route.nodes) do
            log("info", string.format("  %s: score=%d reason=%s",
                n.id or "?", n.score or 0, n.reason or "-"))
        end
    end

    session:execute("bridge", route.sip_gateway)
    return
end

-- ── Ошибка API (нет зарегистрированных нод) ──────────────────────
if route.error then
    log("err", "ошибка балансировщика: " .. route.error)
    safe_hangup()
    return
end

-- ── Успешный маршрут ─────────────────────────────────────────────
if route.sip_gateway then
    log("info", string.format("ROUTE: %s score=%d → %s",
        route.node_id or "?", route.score or 0, route.sip_gateway))

    session:execute("bridge", route.sip_gateway)
    return
end

-- ── Непредвиденная ситуация ──────────────────────────────────────
log("err", "нет sip_gateway в ответе балансировщика")
safe_hangup()
