local LrPathUtils = import "LrPathUtils"
local LrTasks = import "LrTasks"

local M = {}

local json = dofile(LrPathUtils.child(_PLUGIN.path, "lib/JsonLite.lua"))

local function shellEscape(s)
    local text = tostring(s or "")
    text = text:gsub("'", "'\\''")
    return "'" .. text .. "'"
end

local function trim(s)
    return tostring(s or ""):gsub("^%s+", ""):gsub("%s+$", "")
end

local function readFile(path)
    local f = io.open(path, "r")
    if not f then
        return nil
    end
    local body = f:read("*a")
    f:close()
    return body
end

local function writeFile(path, body)
    local f = io.open(path, "w")
    if not f then
        return false
    end
    f:write(body or "")
    f:close()
    return true
end

local function jsonEncode(value)
    local t = type(value)
    if t == "nil" then
        return "null"
    elseif t == "boolean" then
        return value and "true" or "false"
    elseif t == "number" then
        return tostring(value)
    elseif t == "string" then
        local escaped = value
            :gsub("\\", "\\\\")
            :gsub("\"", "\\\"")
            :gsub("\n", "\\n")
            :gsub("\r", "\\r")
            :gsub("\t", "\\t")
        return "\"" .. escaped .. "\""
    elseif t == "table" then
        local isArray = true
        local maxIndex = 0
        for k, _ in pairs(value) do
            if type(k) ~= "number" then
                isArray = false
                break
            end
            if k > maxIndex then
                maxIndex = k
            end
        end
        local parts = {}
        if isArray then
            for i = 1, maxIndex do
                parts[#parts + 1] = jsonEncode(value[i])
            end
            return "[" .. table.concat(parts, ",") .. "]"
        end
        for k, v in pairs(value) do
            parts[#parts + 1] = jsonEncode(tostring(k)) .. ":" .. jsonEncode(v)
        end
        return "{" .. table.concat(parts, ",") .. "}"
    end
    return "null"
end

local function buildCachePath()
    local tempDir = LrPathUtils.getStandardFilePath("temp")
    return LrPathUtils.child(tempDir, "mindfullens_license_entitlement.json")
end

local function postJsonCurl(url, payloadObj, timeoutSec)
    local requestJson = jsonEncode(payloadObj or {})
    local tempDir = LrPathUtils.getStandardFilePath("temp")
    local reqPath = LrPathUtils.child(tempDir, "mindfullens_license_req.json")
    local resPath = LrPathUtils.child(tempDir, "mindfullens_license_res.json")
    writeFile(reqPath, requestJson)

    local timeout = tonumber(timeoutSec) or 8
    local cmd = table.concat({
        "curl -sS",
        "--max-time " .. tostring(timeout),
        "-H " .. shellEscape("Content-Type: application/json"),
        "-X POST",
        "--data-binary @" .. shellEscape(reqPath),
        shellEscape(url),
        "> " .. shellEscape(resPath),
    }, " ")

    local status = LrTasks.execute(cmd)
    if status ~= 0 then
        return false, "curl_failed:" .. tostring(status), nil
    end

    local raw = readFile(resPath)
    if not raw or trim(raw) == "" then
        return false, "empty_response", nil
    end

    local ok, parsed = pcall(function()
        return json.decode(raw)
    end)
    if not ok then
        return false, "invalid_json", raw
    end
    return true, nil, parsed
end

local function makeBasePayload(opts)
    local app = opts.app or {}
    return {
        app = {
            product = tostring(app.product or "analog-signature-lr"),
            major = tonumber(app.major or 1) or 1,
            build = tonumber(app.build or 0) or 0,
            platform = tostring(app.platform or "macos"),
        },
    }
end

function M.activate(baseUrl, licenseKey, deviceId, opts)
    local payload = makeBasePayload(opts or {})
    payload.licenseKey = tostring(licenseKey or "")
    payload.deviceId = tostring(deviceId or "")
    local url = tostring(baseUrl or "") .. "/v1/license/activate"
    return postJsonCurl(url, payload, (opts or {}).timeoutSec)
end

function M.refresh(baseUrl, licenseId, deviceId, tokenValue, opts)
    local payload = makeBasePayload(opts or {})
    payload.licenseId = tostring(licenseId or "")
    payload.deviceId = tostring(deviceId or "")
    payload.token = tostring(tokenValue or "")
    local url = tostring(baseUrl or "") .. "/v1/license/refresh"
    return postJsonCurl(url, payload, (opts or {}).timeoutSec)
end

function M.checkBuild(baseUrl, licenseId, opts)
    local payload = makeBasePayload(opts or {})
    payload.licenseId = tostring(licenseId or "")
    local url = tostring(baseUrl or "") .. "/v1/license/check-build"
    return postJsonCurl(url, payload, (opts or {}).timeoutSec)
end

function M.saveCachedEntitlement(responseObj)
    local cachePath = buildCachePath()
    local body = jsonEncode(responseObj or {})
    return writeFile(cachePath, body), cachePath
end

function M.loadCachedEntitlement()
    local cachePath = buildCachePath()
    local raw = readFile(cachePath)
    if not raw or trim(raw) == "" then
        return false, "missing_cache", nil
    end
    local ok, parsed = pcall(function()
        return json.decode(raw)
    end)
    if not ok then
        return false, "invalid_cache_json", nil
    end
    return true, nil, parsed
end

function M.cachePath()
    return buildCachePath()
end

return M

