"use strict";
require("dotenv").config();
const crypto = require("crypto");
const http = require("http");
const express = require("express");
const KEEZ_ENV = process.env.KEEZ_ENV || "app";
const APPLICATION_ID = process.env.KEEZ_APPLICATION_ID;
const SECRET = process.env.KEEZ_SECRET;
const CLIENT_EID = process.env.KEEZ_CLIENT_EID;
const TOKEN_URL = `https://${KEEZ_ENV}.keez.ro/idp/connect/token`;
const API_BASE = `https://${KEEZ_ENV}.keez.ro/api/v1.0/public-api`;
/** Seria facturii în Keez — fixă */
const INVOICE_SERIES = "A";
const AUTO_CREATE_ITEM_DEFAULT = process.env.KEEZ_AUTO_CREATE_ITEM !== "false";
const ITEM_CURRENCY_DEFAULT = process.env.KEEZ_ITEM_CURRENCY_CODE || "RON";
const ITEM_MEASURE_UNIT_DEFAULT = Number(process.env.KEEZ_ITEM_MEASURE_UNIT_ID || 1);
const ITEM_IS_ACTIVE_DEFAULT = process.env.KEEZ_ITEM_IS_ACTIVE !== "false";
const ITEM_CATEGORY_DEFAULT = process.env.KEEZ_ITEM_CATEGORY_EXTERNAL_ID || "MISCSRV";
/** Dacă e setat, POST /invoices cere `Authorization: Bearer <secret>` sau `X-API-Key: <secret>`. */
const API_SECRET = (process.env.API_SECRET || "").trim();
function constantTimeEqual(a, b) {
    if (typeof a !== "string" || typeof b !== "string")
        return false;
    const bufA = Buffer.from(a, "utf8");
    const bufB = Buffer.from(b, "utf8");
    if (bufA.length !== bufB.length)
        return false;
    return crypto.timingSafeEqual(bufA, bufB);
}
function extractClientApiSecret(req) {
    const key = req.get("x-api-key");
    if (key)
        return String(key).trim();
    const auth = req.get("authorization");
    if (auth && /^Bearer\s+/i.test(auth)) {
        return auth.replace(/^Bearer\s+/i, "").trim();
    }
    return null;
}
function requireApiSecret(req, res, next) {
    if (!API_SECRET) {
        return next();
    }
    const provided = extractClientApiSecret(req);
    if (!provided || !constantTimeEqual(provided, API_SECRET)) {
        return res.status(401).json({
            error: "Neautorizat: setează `Authorization: Bearer <API_SECRET>` sau header `X-API-Key: <API_SECRET>`.",
        });
    }
    next();
}
function requireConfig() {
    const missing = [];
    if (!APPLICATION_ID)
        missing.push("KEEZ_APPLICATION_ID");
    if (!SECRET)
        missing.push("KEEZ_SECRET");
    if (!CLIENT_EID)
        missing.push("KEEZ_CLIENT_EID");
    if (missing.length) {
        throw new Error(`Lipsesc variabile de mediu: ${missing.join(", ")}`);
    }
}
async function readJsonBody(resp) {
    const text = await resp.text();
    if (!text)
        return null;
    try {
        return JSON.parse(text);
    }
    catch {
        return { raw: text };
    }
}
function keezHttpError(status, body) {
    const err = new Error(typeof body === "string" ? body : `Keez HTTP ${status}`);
    err.status = status;
    err.body = body;
    return err;
}
/**
 * Pasul 1 — Keez IDP: application/x-www-form-urlencoded
 */
async function getKeezAuthorizationHeader() {
    const body = new URLSearchParams({
        client_id: `app${APPLICATION_ID}`,
        client_secret: SECRET,
        grant_type: "client_credentials",
        scope: "public-api",
    });
    const resp = await fetch(TOKEN_URL, {
        method: "POST",
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
        body: body.toString(),
    });
    const json = await readJsonBody(resp);
    if (!resp.ok) {
        throw keezHttpError(resp.status, json);
    }
    return `${json.token_type} ${json.access_token}`;
}
async function getItem(authHeader, itemExternalId) {
    const enc = encodeURIComponent(itemExternalId);
    const url = `${API_BASE}/${CLIENT_EID}/items/${enc}`;
    const resp = await fetch(url, {
        headers: { Authorization: authHeader },
    });
    const json = await readJsonBody(resp);
    if (resp.status === 404)
        return null;
    if (resp.status === 400) {
        const msg = `${json?.Message ?? ""} ${JSON.stringify(json?.errors ?? "")}`.toLowerCase();
        if (msg.includes("nu exista"))
            return null;
        throw keezHttpError(resp.status, json);
    }
    if (!resp.ok)
        throw keezHttpError(resp.status, json);
    return json;
}
async function listItemsByName(authHeader, itemName) {
    const params = new URLSearchParams({
        filter: `name:${itemName}`,
        count: "50",
        offset: "0",
    });
    const url = `${API_BASE}/${CLIENT_EID}/items?${params}`;
    const resp = await fetch(url, { headers: { Authorization: authHeader } });
    const json = await readJsonBody(resp);
    if (!resp.ok)
        throw keezHttpError(resp.status, json);
    return json;
}
function extractItemExternalIdFromListResponse(data, itemName) {
    let candidates = [];
    if (Array.isArray(data)) {
        candidates = data;
    }
    else if (data && typeof data === "object") {
        for (const key of ["items", "results", "data", "content"]) {
            if (Array.isArray(data[key])) {
                candidates = data[key];
                break;
            }
        }
    }
    const normalizedName = String(itemName).trim().toLowerCase();
    for (const item of candidates) {
        if (!item || typeof item !== "object")
            continue;
        const currentName = String(item.name ?? "").trim().toLowerCase();
        if (currentName === normalizedName) {
            const externalId = item.externalId ?? item.itemExternalId ?? item.id;
            if (externalId != null)
                return String(externalId);
        }
    }
    if (candidates.length === 1 && candidates[0] && typeof candidates[0] === "object") {
        const item = candidates[0];
        const externalId = item.externalId ?? item.itemExternalId ?? item.id;
        if (externalId != null)
            return String(externalId);
    }
    return null;
}
function buildCreateItemPayload(itemName, itemCreate = {}) {
    const categoryExternalId = itemCreate.categoryExternalId ?? ITEM_CATEGORY_DEFAULT;
    if (!categoryExternalId) {
        throw Object.assign(new Error("Lipsește categoryExternalId pentru creare articol (setează în body.itemCreate sau KEEZ_ITEM_CATEGORY_EXTERNAL_ID)"), { status: 400 });
    }
    return {
        name: itemName,
        currencyCode: itemCreate.currencyCode ?? ITEM_CURRENCY_DEFAULT,
        measureUnitId: Number(itemCreate.measureUnitId ?? ITEM_MEASURE_UNIT_DEFAULT),
        isActive: itemCreate.isActive !== undefined
            ? Boolean(itemCreate.isActive)
            : ITEM_IS_ACTIVE_DEFAULT,
        categoryExternalId,
    };
}
function extractCreatedExternalId(data) {
    if (typeof data === "string")
        return data;
    if (data && typeof data === "object") {
        return (data.externalId ?? data.itemExternalId ?? data.id ?? null);
    }
    return null;
}
async function createItem(authHeader, payload) {
    const url = `${API_BASE}/${CLIENT_EID}/items`;
    const resp = await fetch(url, {
        method: "POST",
        headers: {
            Authorization: authHeader,
            "Content-Type": "application/json",
        },
        body: JSON.stringify(payload),
    });
    const json = await readJsonBody(resp);
    if (!resp.ok)
        throw keezHttpError(resp.status, json);
    return json;
}
/**
 * Același flux ca script.py: după externalId → după nume → creare (dacă e permis).
 */
async function resolveItemExternalId(authHeader, { itemExternalId, itemName, autoCreate, itemCreate }, cache) {
    const cacheKey = `${itemExternalId}\0${itemName}`;
    if (cache.has(cacheKey))
        return cache.get(cacheKey);
    const existingById = await getItem(authHeader, itemExternalId);
    if (existingById) {
        cache.set(cacheKey, itemExternalId);
        return itemExternalId;
    }
    const searchResult = await listItemsByName(authHeader, itemName);
    const foundId = extractItemExternalIdFromListResponse(searchResult, itemName);
    if (foundId) {
        cache.set(cacheKey, foundId);
        return foundId;
    }
    if (!autoCreate) {
        throw Object.assign(new Error(`Articolul "${itemExternalId}" nu există; căutare după nume "${itemName}" fără rezultat, iar auto-create este dezactivat.`), { status: 400 });
    }
    const createPayload = buildCreateItemPayload(itemName, itemCreate);
    const created = await createItem(authHeader, createPayload);
    const newId = extractCreatedExternalId(created);
    if (!newId) {
        throw Object.assign(new Error("Articol creat în Keez, dar răspunsul nu conține externalId."), { status: 502 });
    }
    const idStr = String(newId);
    cache.set(cacheKey, idStr);
    return idStr;
}
async function postKeezInvoice(authorizationHeader, invoiceBody) {
    const url = `${API_BASE}/${CLIENT_EID}/invoices`;
    const resp = await fetch(url, {
        method: "POST",
        headers: {
            Authorization: authorizationHeader,
            "Content-Type": "application/json",
        },
        body: JSON.stringify(invoiceBody),
    });
    const json = await readJsonBody(resp);
    if (!resp.ok) {
        const err = new Error(`Keez invoice ${resp.status}`);
        err.status = resp.status;
        err.body = json;
        throw err;
    }
    return json;
}
function todayYyyyMmDdInt() {
    const d = new Date();
    const y = d.getFullYear();
    const m = String(d.getMonth() + 1).padStart(2, "0");
    const day = String(d.getDate()).padStart(2, "0");
    return parseInt(`${y}${m}${day}`, 10);
}
/**
 * Aplicația Express + server HTTP (același pattern ca core-be: createServer(app)).
 */
class App {
    constructor() {
        this.app = express();
        this.config();
        this.server = http.createServer(this.app);
    }
    config() {
        this.app.use(express.json({ limit: "1mb" }));
        this.app.get("/health", (_req, res) => {
            res.json({ ok: true });
        });
        /**
         * POST /invoices — vezi comentariul din implementare.
         */
        this.app.post("/invoices", requireApiSecret, async (req, res) => {
            try {
                requireConfig();
            }
            catch (e) {
                return res.status(500).json({ error: e.message });
            }
            const body = req.body ?? {};
            const { documentDate, dueDate, vatOnCollection = false, currencyCode = "RON", paymentTypeId = 1, partner, invoiceDetails, resolveItems = true, } = body;
            if (!partner || !Array.isArray(invoiceDetails) || invoiceDetails.length === 0) {
                return res.status(400).json({
                    error: "Body invalid: sunt obligatorii `partner` și `invoiceDetails` (array nevid).",
                });
            }
            const docDate = documentDate ?? todayYyyyMmDdInt();
            const due = dueDate ?? docDate;
            try {
                const auth = await getKeezAuthorizationHeader();
                const cache = new Map();
                let keezInvoiceDetails;
                if (resolveItems === false) {
                    keezInvoiceDetails = invoiceDetails.map((line) => {
                        const { itemExternalId, quantity, unitPrice } = line;
                        if (itemExternalId == null || quantity == null || unitPrice == null) {
                            throw Object.assign(new Error("Pe fiecare linie: itemExternalId, quantity, unitPrice (când resolveItems este false)."), { status: 400 });
                        }
                        return {
                            itemExternalId: String(itemExternalId),
                            quantity: Number(quantity),
                            unitPrice: Number(unitPrice),
                        };
                    });
                }
                else {
                    keezInvoiceDetails = await Promise.all(invoiceDetails.map(async (line) => {
                        const { itemExternalId, itemName, quantity, unitPrice, itemCreate, autoCreateItem, } = line;
                        if (itemExternalId == null ||
                            !String(itemName || "").trim() ||
                            quantity == null ||
                            unitPrice == null) {
                            throw Object.assign(new Error("Cu resolveItems=true: pe fiecare linie sunt obligatorii itemExternalId, itemName, quantity, unitPrice."), { status: 400 });
                        }
                        const autoCreate = autoCreateItem !== undefined
                            ? Boolean(autoCreateItem)
                            : AUTO_CREATE_ITEM_DEFAULT;
                        const resolvedId = await resolveItemExternalId(auth, {
                            itemExternalId: String(itemExternalId),
                            itemName: String(itemName).trim(),
                            autoCreate,
                            itemCreate: itemCreate && typeof itemCreate === "object" ? itemCreate : {},
                        }, cache);
                        return {
                            itemExternalId: resolvedId,
                            quantity: Number(quantity),
                            unitPrice: Number(unitPrice),
                        };
                    }));
                }
                const keezBody = {
                    series: INVOICE_SERIES,
                    documentDate: docDate,
                    dueDate: due,
                    vatOnCollection: Boolean(vatOnCollection),
                    currencyCode,
                    paymentTypeId: Number(paymentTypeId),
                    partner,
                    invoiceDetails: keezInvoiceDetails,
                };
                const result = await postKeezInvoice(auth, keezBody);
                return res.status(201).json(result);
            }
            catch (e) {
                const status = e.status && Number.isInteger(e.status) ? e.status : 502;
                return res.status(status).json({
                    error: e.message,
                    details: e.body ?? undefined,
                });
            }
        });
    }
}
module.exports = new App();
//# sourceMappingURL=app.js.map