#pragma once
#include <algorithm>
#include <cmath>
#include <ctime>
#include <chrono>
#include <iomanip>
#include <map>
#include <optional>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
#include "delivery.hpp"
#include "shop.hpp"
#include "../db/pool.hpp"
#include "../config.hpp"
#include "nlohmann/json.hpp"

namespace order {

struct Defaults;

namespace {

struct Config {
    int kBenutzer = 1;
    int kFirmaHistory = 0;
    int kSprache = 1;
    int kPlattform = 7;
    int kVersandArt = 0;
    int kKundengruppe = 0;
    int orderNumberSequence = 3;
    int customerNumberSequence = 6;
};

Config g_config;
std::unique_ptr<Defaults> g_resolved_defaults;
std::map<std::string, nlohmann::json> g_zahlungsart_cache;

} // namespace

struct Defaults {
    int kFirmaHistory = 1;
    int kVersandArt = 1;
    int kKundengruppe = 1;
    int kPlattform = 1;
};

inline void load_config() {
    g_config.kBenutzer = config::get_int("JTL_KBENUTZER", 1);
    g_config.kFirmaHistory = config::get_int("JTL_KFIRMAHISTORY", 0);
    g_config.kSprache = config::get_int("JTL_KSPRACHE", 1);
    g_config.kPlattform = config::get_int("JTL_KPLATTFORM", 7);
    g_config.kVersandArt = config::get_int("JTL_KVERSANDART", 0);
    g_config.kKundengruppe = config::get_int("JTL_KKUNDENGRUPPE", 0);
    g_config.orderNumberSequence = config::get_int("JTL_ORDER_NUMBER_SEQUENCE", 3);
    g_config.customerNumberSequence = config::get_int("JTL_CUSTOMER_NUMBER_SEQUENCE", 6);
}

inline const Defaults& get_defaults(OdbcPool::Connection* c) {
    if (g_resolved_defaults) return *g_resolved_defaults;

    Defaults d;
    d.kFirmaHistory = g_config.kFirmaHistory;
    d.kVersandArt = g_config.kVersandArt;
    d.kKundengruppe = g_config.kKundengruppe;
    d.kPlattform = g_config.kPlattform;

    const char* sql =
        "SELECT"
        " (SELECT MAX(kFirmaHistory) FROM dbo.tFirmaHistory) AS kFirmaHistory,"
        " (SELECT MIN(kVersandArt) FROM dbo.tVersandArt) AS kVersandArt,"
        " (SELECT TOP 1 kKundenGruppe FROM dbo.tKundenGruppe ORDER BY nStandard DESC, kKundenGruppe) AS kKundengruppe,"
        " (SELECT CASE WHEN EXISTS (SELECT 1 FROM dbo.tPlattform WHERE nPlattform = ?) THEN ? ELSE 1 END) AS kPlattform";

    std::vector<Param> ps = {
        {ParamType::Int, "", g_config.kPlattform},
        {ParamType::Int, "", g_config.kPlattform}
    };
    ResultSet rs;
    if (get_pool().execute(c, sql, ps, rs) && !rs.empty() && !rs[0].empty()) {
        if (!rs[0][0].str.empty()) d.kFirmaHistory = std::stoi(rs[0][0].str);
        if (!rs[0][1].str.empty()) d.kVersandArt = std::stoi(rs[0][1].str);
        if (!rs[0][2].str.empty()) d.kKundengruppe = std::stoi(rs[0][2].str);
        if (!rs[0][3].str.empty()) d.kPlattform = std::stoi(rs[0][3].str);
    }

    // Apply environment overrides
    if (g_config.kFirmaHistory > 0) d.kFirmaHistory = g_config.kFirmaHistory;
    if (g_config.kVersandArt > 0) d.kVersandArt = g_config.kVersandArt;
    if (g_config.kKundengruppe > 0) d.kKundengruppe = g_config.kKundengruppe;

    g_resolved_defaults = std::make_unique<Defaults>(d);
    return *g_resolved_defaults;
}

inline double to_number(const nlohmann::json& value, double fallback = 0) {
    if (value.is_null()) return fallback;
    if (value.is_number()) return value.get<double>();
    if (value.is_string()) {
        std::string s = value.get<std::string>();
        if (s.empty()) return fallback;
        std::replace(s.begin(), s.end(), ',', '.');
        try { return std::stod(s); } catch (...) {}
    }
    return fallback;
}

inline int to_int(const nlohmann::json& value, int fallback = 0) {
    if (value.is_null()) return fallback;
    if (value.is_number_integer()) return value.get<int>();
    if (value.is_number_unsigned()) return static_cast<int>(value.get<unsigned>());
    if (value.is_number_float()) return static_cast<int>(value.get<double>());
    if (value.is_boolean()) return value.get<bool>() ? 1 : 0;
    if (value.is_string()) {
        std::string s = value.get<std::string>();
        if (s.empty()) return fallback;
        try { return std::stoi(s); } catch (...) {}
    }
    return fallback;
}

inline int json_int(const nlohmann::json& obj, const char* key, int fallback = 0) {
    auto it = obj.find(key);
    if (it == obj.end()) return fallback;
    return to_int(*it, fallback);
}

inline int steuerklasse_for_vat(double vat) {
    if (vat >= 15) return 1;
    if (vat > 0) return 2;
    return 1;
}

inline int iso_week(const std::tm& tm) {
    // JTL placeholders: J = Jahr, M = Monat, T = Tag, K = Kalenderwoche.
    // ISO week date
    std::tm t0 = tm;
    int yday = t0.tm_yday;
    int wday = t0.tm_wday;
    if (wday == 0) wday = 7;
    int week = (yday - wday + 10) / 7;
    if (week < 1) week = 1;
    if (week > 53) week = 53;
    return week;
}

inline std::string format_number_placeholders(const std::string& tpl, const std::tm& tm) {
    if (tpl.empty()) return "";
    std::string r = tpl;
    auto replace = [&r](const std::string& from, const std::string& to) {
        size_t start = 0;
        while ((start = r.find(from, start)) != std::string::npos) {
            r.replace(start, from.size(), to);
            start += to.size();
        }
    };
    std::ostringstream mm, dd, kk;
    mm << std::setw(2) << std::setfill('0') << (tm.tm_mon + 1);
    dd << std::setw(2) << std::setfill('0') << tm.tm_mday;
    kk << std::setw(2) << std::setfill('0') << iso_week(tm);
    replace("<J>", std::to_string(tm.tm_year + 1900));
    replace("<M>", mm.str());
    replace("<T>", dd.str());
    replace("<K>", kk.str());
    return r;
}

inline std::tm parse_order_date(const std::string& s) {
    std::tm tm{};
    std::string iso = s;
    std::replace(iso.begin(), iso.end(), ' ', 'T');
    std::istringstream ss(iso);
    ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S");
    if (ss.fail()) {
        auto now = std::chrono::system_clock::now();
        std::time_t t = std::chrono::system_clock::to_time_t(now);
        localtime_r(&t, &tm);
    }
    return tm;
}

inline std::string order_date_sql(const std::tm& tm) {
    char buf[32];
    std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm);
    return buf;
}

inline std::string country_name(const std::string& iso) {
    static const std::map<std::string, std::string> names = {
        {"DE", "Deutschland"},
        {"AT", "Oesterreich"},
        {"CH", "Schweiz"}
    };
    auto it = names.find(iso);
    return it != names.end() ? it->second : iso;
}

inline std::string next_number_from_sequence(OdbcPool::Connection* c, int kLaufendeNummer,
                                             const std::tm& date) {
    const char* sql =
        "DECLARE @n INT, @cPrefix NVARCHAR(50), @cSuffix NVARCHAR(50);"
        "UPDATE dbo.tLaufendeNummern"
        " SET @n = nNummer = nNummer + 1, @cPrefix = cPrefix, @cSuffix = cSuffix"
        " WHERE kLaufendeNummer = ?;"
        "SELECT @n AS nNummer, @cPrefix AS cPrefix, @cSuffix AS cSuffix";

    std::vector<Param> ps = {{ParamType::Int, "", kLaufendeNummer}};
    ResultSet rs;
    if (!get_pool().execute(c, sql, ps, rs) || rs.empty() || rs[0].size() < 3) {
        throw std::runtime_error("dbo.tLaufendeNummern has no row " + std::to_string(kLaufendeNummer));
    }
    int n = std::stoi(rs[0][0].str);
    std::string prefix = format_number_placeholders(rs[0][1].str, date);
    std::string suffix = format_number_placeholders(rs[0][2].str, date);
    return prefix + std::to_string(n) + suffix;
}

inline int allocate_pk(OdbcPool::Connection* c, const std::string& tableName) {
    const char* sql =
        "DECLARE @pk INT;"
        "UPDATE dbo.tpk SET @pk = nummer, nummer = nummer + 1, dChanged = GETDATE() WHERE cName = ?;"
        "SELECT @pk AS pk";

    std::vector<Param> ps = {{ParamType::NVarChar, tableName, 0}};
    ResultSet rs;
    if (!get_pool().execute(c, sql, ps, rs) || rs.empty() || rs[0].empty()) {
        throw std::runtime_error("dbo.tpk has no row for table '" + tableName + "'");
    }
    return std::stoi(rs[0][0].str);
}

inline nlohmann::json resolve_zahlungsart(OdbcPool::Connection* c, const std::string& name) {
    std::string key = name.empty() ? "bar" : name;
    std::transform(key.begin(), key.end(), key.begin(), [](unsigned char ch) { return std::tolower(ch); });
    auto it = g_zahlungsart_cache.find(key);
    if (it != g_zahlungsart_cache.end()) return it->second;

    std::vector<Param> ps = {{ParamType::NVarChar, key, 0}};
    ResultSet rs;
    get_pool().execute(c, "SELECT TOP 1 kZahlungsart, cName FROM dbo.tZahlungsart WHERE cName = ?", ps, rs);

    if (!rs.empty() && !rs[0].empty()) {
        nlohmann::json z = {{"kZahlungsart", std::stoi(rs[0][0].str)}, {"cName", rs[0][1].str}};
        g_zahlungsart_cache[key] = z;
        return z;
    }

    int kZahlungsart = allocate_pk(c, "tZahlungsart");
    const char* insert =
        "INSERT INTO dbo.tZahlungsart"
        " (kZahlungsart, cName, cPrtString, nLastschrift, cPrtStringVor, cPaymentOption, cKonto,"
        " nAusliefernVorZahlung, nPrioritaet, nMahnwesenAktiv, fSkontoWert, nSkontoZeitraum,"
        " nMatchingOptionen, nIstStandard, nAktiv)"
        " VALUES (?, ?, '', 0, '', '', '', 0, 0, 0, 0, 0, 0, 0, 1)";
    std::vector<Param> ps2 = {
        {ParamType::Int, "", kZahlungsart},
        {ParamType::NVarChar, key, 0}
    };
    get_pool().execute(c, insert, ps2, rs);

    nlohmann::json z = {{"kZahlungsart", kZahlungsart}, {"cName", key}};
    g_zahlungsart_cache[key] = z;
    return z;
}

inline std::string next_customer_number(OdbcPool::Connection* c) {
    std::tm date;
    {
        auto now = std::chrono::system_clock::now();
        std::time_t t = std::chrono::system_clock::to_time_t(now);
        localtime_r(&t, &date);
    }
    return next_number_from_sequence(c, g_config.customerNumberSequence, date);
}

inline std::pair<int,int> lookup_kassenkunde(OdbcPool::Connection* c, const Defaults& defaults) {
    ResultSet rs;
    get_pool().execute(c,
        "SELECT TOP 1 kKunde, kKundenGruppe FROM dbo.tKunde WHERE cKassenKunde = 'Y' ORDER BY kKunde", rs);
    if (!rs.empty() && !rs[0].empty()) {
        int kKunde = std::stoi(rs[0][0].str);
        int grp = defaults.kKundengruppe;
        if (rs[0].size() > 1 && !rs[0][1].str.empty()) grp = std::stoi(rs[0][1].str);
        return {kKunde, grp};
    }
    return {0, defaults.kKundengruppe};
}

inline bool is_walk_in_order(const nlohmann::json& order) {
    std::string customer_number = order.value("customerNumber", "");
    if (customer_number.empty() || customer_number == "0") return true;

    const auto billing = order.value("billingAddress", nlohmann::json::object());
    return billing.value("lastName", "") == "Laufkunde"
        && billing.value("firstName", "").empty();
}

inline std::pair<int,int> create_customer(OdbcPool::Connection* c, const std::string& customer_number,
                                          const nlohmann::json& address, const Defaults& defaults) {
    const nlohmann::json a = address.is_null() ? nlohmann::json::object() : address;
    std::string iso = a.value("countryIso", "DE");
    std::transform(iso.begin(), iso.end(), iso.begin(), [](unsigned char ch) { return std::toupper(ch); });
    int kKundengruppe = json_int(a, "customerGroupId", 0);
    if (kKundengruppe <= 0) kKundengruppe = defaults.kKundengruppe;

    const char* sql =
        "DECLARE @returnValue INT;"
        "DECLARE @p1 dbo.TYPE_spkundeInsert;"
        "INSERT INTO @p1"
        " (kInetKunde, kKundenKategorie, cKundenNr, cFirma, cAnrede, cTitel, cVorname, cName,"
        " cStrasse, cPLZ, cOrt, cLand, cTel, cFax, cEMail, dErstellt, cMobil, fRabatt, cUSTID, cNewsletter,"
        " cZusatz, cEbayName, kBuyer, cAdressZusatz, cGeburtstag, cWWW, cSperre, cPostID, kKundenGruppe,"
        " nZahlungsziel, kSprache, cISO, cBundesland, cHerkunft, cKassenKunde, cHRNr, kZahlungsart,"
        " nDebitorennr, cSteuerNr, nKreditlimit, kKundenDrucktext, nMahnstopp, nMahnrhythmus, kFirma,"
        " fProvision, nVertreter, fSkonto, nSkontoInTagen)"
        " VALUES"
        " (0, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, GETDATE(), ?, ?, NULL, 'N',"
        " N'', N'', 0, ?, ?, N'', 'N', NULL, ?,"
        " 0, ?, ?, ?, ?, ?, N'', 0,"
        " ?, N'', 0, 0, 0, 0, 0,"
        " NULL, 0, 0, 0);"
        "EXEC @returnValue = Kunde.spKundeInsert @daten = @p1;"
        "SELECT @returnValue AS kKunde";

    std::vector<Param> ps = {
        {ParamType::NVarChar, customer_number, 0},
        {ParamType::NVarChar, a.value("company", ""), 0},
        {ParamType::NVarChar, a.value("salutation", ""), 0},
        {ParamType::NVarChar, a.value("title", ""), 0},
        {ParamType::NVarChar, a.value("firstName", ""), 0},
        {ParamType::NVarChar, a.value("lastName", "Laufkunde"), 0},
        {ParamType::NVarChar, a.value("street", "-"), 0},
        {ParamType::NVarChar, a.value("zipCode", ""), 0},
        {ParamType::NVarChar, a.value("city", "-"), 0},
        {ParamType::NVarChar, country_name(iso), 0},
        {ParamType::NVarChar, a.value("phone", ""), 0},
        {ParamType::NVarChar, a.value("fax", ""), 0},
        {ParamType::NVarChar, a.value("email", ""), 0},
        {ParamType::NVarChar, a.value("mobile", ""), 0},
        {ParamType::Double, "", 0, to_number(a["discount"], 0)},
        {ParamType::NVarChar, a.value("addressAddition", ""), 0},
        {ParamType::NVarChar, a.value("birthday", ""), 0},
        {ParamType::Int, "", kKundengruppe},
        {ParamType::Int, "", g_config.kSprache},
        {ParamType::NVarChar, iso, 0},
        {ParamType::NVarChar, a.value("state", ""), 0},
        {ParamType::NVarChar, std::string("Kasse"), 0},
        {ParamType::NVarChar, std::string("Y"), 0},
        {ParamType::Int, "", json_int(a, "debtorNumber", 0)}
    };
    ResultSet rs;
    if (!get_pool().execute(c, sql, ps, rs) || rs.empty() || rs[0].empty()) {
        throw std::runtime_error("Kunde.spKundeInsert failed for '" + customer_number + "'");
    }
    int kKunde = std::stoi(rs[0][0].str);
    if (kKunde <= 0) throw std::runtime_error("Kunde.spKundeInsert returned invalid kKunde");
    return {kKunde, kKundengruppe};
}

inline std::pair<int,int> resolve_customer(OdbcPool::Connection* c, const nlohmann::json& order,
                                          const Defaults& defaults) {
    if (is_walk_in_order(order)) {
        auto [kKunde, grp] = lookup_kassenkunde(c, defaults);
        if (kKunde > 0) return {kKunde, grp};
        return create_customer(c, next_customer_number(c),
            order.value("billingAddress", nlohmann::json::object()), defaults);
    }

    std::string customer_number = order.value("customerNumber", "");
    std::vector<Param> ps = {{ParamType::NVarChar, customer_number, 0}};
    ResultSet rs;
    get_pool().execute(c, "SELECT TOP 1 kKunde, kKundenGruppe FROM dbo.tKunde WHERE cKundenNr = ?", ps, rs);
    if (!rs.empty() && !rs[0].empty()) {
        int kKunde = std::stoi(rs[0][0].str);
        int grp = defaults.kKundengruppe;
        if (rs[0].size() > 1 && !rs[0][1].str.empty()) grp = std::stoi(rs[0][1].str);
        return {kKunde, grp};
    }
    return create_customer(c, customer_number, order.value("billingAddress", nlohmann::json::object()), defaults);
}

inline std::string next_order_number(OdbcPool::Connection* c, const std::tm& date) {
    return next_number_from_sequence(c, g_config.orderNumberSequence, date);
}

inline void insert_order_address(OdbcPool::Connection* c, int kAuftrag, int kKunde,
                                 const nlohmann::json& address, int nTyp) {
    const nlohmann::json a = address.is_null() ? nlohmann::json::object() : address;
    std::string iso = a.value("countryIso", "DE");
    std::transform(iso.begin(), iso.end(), iso.begin(), [](unsigned char ch) { return std::toupper(ch); });

    const char* sql =
        "INSERT INTO Verkauf.tAuftragAdresse"
        " (kAuftrag, kKunde, cFirma, cAnrede, cTitel, cVorname, cName, cStrasse, cPLZ, cOrt, cLand,"
        " cTel, cZusatz, cAdressZusatz, cMobil, cMail, cFax, cBundesland, cISO, nTyp, nZolldokumenteErforderlich)"
        " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)";

    std::vector<Param> ps = {
        {ParamType::Int, "", kAuftrag},
        {ParamType::Int, "", kKunde},
        {ParamType::NVarChar, a.value("company", ""), 0},
        {ParamType::NVarChar, a.value("salutation", ""), 0},
        {ParamType::NVarChar, a.value("title", ""), 0},
        {ParamType::NVarChar, a.value("firstName", ""), 0},
        {ParamType::NVarChar, a.value("lastName", "-"), 0},
        {ParamType::NVarChar, a.value("street", "-"), 0},
        {ParamType::NVarChar, a.value("zipCode", ""), 0},
        {ParamType::NVarChar, a.value("city", "-"), 0},
        {ParamType::NVarChar, country_name(iso), 0},
        {ParamType::NVarChar, a.value("phone", ""), 0},
        {ParamType::NVarChar, a.value("extraAddressLine", ""), 0},
        {ParamType::NVarChar, a.value("addressAddition", ""), 0},
        {ParamType::NVarChar, a.value("mobile", ""), 0},
        {ParamType::NVarChar, a.value("email", ""), 0},
        {ParamType::NVarChar, a.value("fax", ""), 0},
        {ParamType::NVarChar, a.value("state", ""), 0},
        {ParamType::NVarChar, iso, 0},
        {ParamType::Int, "", nTyp}
    };
    ResultSet rs;
    get_pool().execute(c, sql, ps, rs);
}

inline int insert_order_item(OdbcPool::Connection* c, int kAuftrag, const nlohmann::json& item) {
    double vat = to_number(item["vat"], 19);
    double quantity = to_number(item["quantity"], 1);
    double price_gross = to_number(item["priceGross"], 0);
    double price_net = to_number(item["priceNet"], price_gross / (1 + vat / 100));
    double discount = to_number(item["discountPercent"], 0);
    int kSteuerklasse = steuerklasse_for_vat(vat);
    std::string sku = item.value("sku", "");

    int kArtikel = 0;
    bool has_artikel = false;
    if (!sku.empty()) {
        std::vector<Param> ps = {{ParamType::NVarChar, sku, 0}};
        ResultSet rs;
        get_pool().execute(c, "SELECT TOP 1 kArtikel FROM dbo.tArtikel WHERE cArtNr = ?", ps, rs);
        if (!rs.empty() && !rs[0].empty()) {
            kArtikel = std::stoi(rs[0][0].str);
            has_artikel = true;
        }
    }

    const char* sql =
        "DECLARE @t TABLE ([kAuftragPosition] INT);"
        "INSERT INTO Verkauf.tAuftragPosition"
        " (kArtikel, kAuftrag, cArtNr, nReserviert, cName, cHinweis, fAnzahl, fVkNetto, fMwSt,"
        " cNameStandard, kSteuerklasse, nType, cEinheit, fFaktor, kSteuerschluessel, fRabatt)"
        " OUTPUT inserted.kAuftragPosition INTO @t"
        " VALUES (?, ?, ?, 1, ?, ?, ?, ?, ?,"
        " ?, ?, ?, ?, 1.0, 3, ?);"
        "SELECT kAuftragPosition FROM @t";

    Param cartnr{ParamType::NVarChar, has_artikel ? sku : std::string(), 0, 0.0, !has_artikel};

    Param pk_art{ParamType::Int, "", kArtikel, 0.0, !has_artikel};

    std::string name = item.value("name", sku.empty() ? std::string("Position") : sku);
    std::vector<Param> ps = {
        pk_art,
        {ParamType::Int, "", kAuftrag},
        cartnr,
        {ParamType::NVarChar, name, 0},
        {ParamType::NVarChar, item.value("note", ""), 0},
        {ParamType::Double, "", 0, quantity},
        {ParamType::Double, "", 0, price_net},
        {ParamType::Double, "", 0, vat},
        {ParamType::NVarChar, name, 0},
        {ParamType::Int, "", kSteuerklasse},
        {ParamType::Int, "", has_artikel ? 1 : 0},
        {ParamType::NVarChar, item.value("unit", ""), 0},
        {ParamType::Double, "", 0, discount}
    };
    ResultSet rs;
    if (!get_pool().execute(c, sql, ps, rs) || rs.empty() || rs[0].empty()) return 0;
    return std::stoi(rs[0][0].str);
}

inline int parse_pos_auftrag_id(const std::string& external_id) {
    if (external_id.empty()) return 0;
    try { return std::stoi(external_id); } catch (...) { return 0; }
}

// PosOrderCreationService.CheckIfOrderExists — already-imported POS order.
inline std::optional<std::pair<int, std::string>> find_existing_pos_order(
        OdbcPool::Connection* c, int kPosAuftrag) {
    int kShopSubShop = get_active_shop_subshop_id();
    if (kPosAuftrag <= 0 || kShopSubShop <= 0) return std::nullopt;

    const char* sql =
        "SELECT TOP 1 m.kAuftrag, a.cAuftragsNr FROM Pos.tAuftragMapping m "
        "LEFT JOIN Verkauf.tAuftrag a ON a.kAuftrag = m.kAuftrag "
        "WHERE m.kPosAuftrag = ? AND m.kShopSubShop = ? AND m.kAuftrag IS NOT NULL "
        "ORDER BY m.kAuftrag DESC";

    ResultSet rs;
    std::vector<Param> ps = {
        {ParamType::Int, "", kPosAuftrag},
        {ParamType::Int, "", kShopSubShop},
    };
    if (!get_pool().execute(c, sql, ps, rs)
        || rs.empty() || rs[0].empty() || rs[0][0].type == CellType::Null) {
        return std::nullopt;
    }

    int kAuftrag = std::stoi(rs[0][0].str);
    if (kAuftrag <= 0) return std::nullopt;
    std::string order_number = rs[0].size() > 1 ? rs[0][1].str : "";
    return std::make_pair(kAuftrag, order_number);
}

inline void upsert_pos_order_mapping(OdbcPool::Connection* c, int kAuftrag, int kPosAuftrag) {
    int kShopSubShop = get_active_shop_subshop_id();
    if (kPosAuftrag <= 0 || kShopSubShop <= 0) return;

    const char* sql =
        "MERGE INTO Pos.tAuftragMapping WITH (HOLDLOCK) AS Target "
        "USING (SELECT ? AS kAuftrag, ? AS kPosAuftrag, ? AS kShopSubShop) AS Source "
        "ON Target.kPosAuftrag = Source.kPosAuftrag AND Target.kShopSubShop = Source.kShopSubShop "
        "WHEN MATCHED THEN UPDATE SET Target.kAuftrag = Source.kAuftrag "
        "WHEN NOT MATCHED BY TARGET THEN "
        "INSERT (kAuftrag, kPosAuftrag, kShopSubShop) "
        "VALUES (Source.kAuftrag, Source.kPosAuftrag, Source.kShopSubShop)";

    std::vector<Param> ps = {
        {ParamType::Int, "", kAuftrag},
        {ParamType::Int, "", kPosAuftrag},
        {ParamType::Int, "", kShopSubShop}
    };
    ResultSet rs;
    get_pool().execute(c, sql, ps, rs);
}

inline void insert_pos_order_position_mapping(OdbcPool::Connection* c, int kAuftragPosition,
                                               const std::string& external_id) {
    int kPosAuftragPosition = 0;
    try { kPosAuftragPosition = std::stoi(external_id); } catch (...) { return; }
    int kShopSubShop = get_active_shop_subshop_id();
    if (kAuftragPosition <= 0 || kPosAuftragPosition <= 0 || kShopSubShop <= 0) return;

    const char* sql = "INSERT INTO Pos.tAuftragPositionMapping (kAuftragPosition, kPosAuftragPosition, kShopSubShop) VALUES (?, ?, ?)";
    std::vector<Param> ps = {
        {ParamType::Int, "", kAuftragPosition},
        {ParamType::Int, "", kPosAuftragPosition},
        {ParamType::Int, "", kShopSubShop}
    };
    ResultSet rs;
    get_pool().execute(c, sql, ps, rs);
}

inline bool is_order_delivered(const nlohmann::json& order) {
    auto it = order.find("settings");
    if (it != order.end() && !it->is_null()) {
        auto del = it->find("deliver");
        if (del != it->end()) {
            if (del->is_boolean()) return del->get<bool>();
            if (del->is_number()) return del->get<int>() != 0;
            if (del->is_string()) {
                std::string s = *del;
                std::transform(s.begin(), s.end(), s.begin(), [](unsigned char ch) { return std::tolower(ch); });
                return s == "true" || s == "1";
            }
            return false;
        }
    }
    return true;
}

inline void insert_payment(OdbcPool::Connection* c, int kAuftrag, const nlohmann::json& payment,
                          const nlohmann::json& order, const std::tm& order_date_tm) {
    std::string payment_name = payment.value("paymentMethodName", "");
    if (payment_name.empty()) payment_name = order.value("paymentMethodName", "Bar");
    nlohmann::json zahlungsart = resolve_zahlungsart(c, payment_name);
    int kZahlung = allocate_pk(c, "tZahlung");

    const char* sql =
        "INSERT INTO dbo.tZahlung"
        " (kZahlung, cName, dDatum, fBetrag, kBestellung, kBenutzer, nAnzahlung, cHinweis, kZahlungsart,"
        " nKeinExport, cExternalTransactionId, nZuweisungstyp, nZahlungstyp, cZuweisungsinfo, nZuweisungswertung)"
        " VALUES (?, ?, ?, ?, ?, ?, 0, '', ?,"
        " 0, ?, 0, 0, '', 0)";

    std::vector<Param> ps = {
        {ParamType::Int, "", kZahlung},
        {ParamType::NVarChar, zahlungsart.value("cName", ""), 0},
        {ParamType::NVarChar, order_date_sql(order_date_tm), 0},
        {ParamType::Double, "", 0, to_number(payment["amount"], 0)},
        {ParamType::Int, "", kAuftrag},
        {ParamType::Int, "", g_config.kBenutzer},
        {ParamType::Int, "", json_int(zahlungsart, "kZahlungsart", 0)},
        {ParamType::NVarChar, order.value("externalOrderNumber", ""), 0}
    };
    ResultSet rs;
    get_pool().execute(c, sql, ps, rs);
}

inline nlohmann::json create_order(const nlohmann::json& order) {
    load_config();

    auto guard = get_pool().checkout();
    if (!guard) throw std::runtime_error("no ODBC connection available");

    auto* c = guard.get();

    int kPosAuftrag = parse_pos_auftrag_id(order.value("externalId", ""));
    if (kPosAuftrag > 0) {
        if (auto existing = find_existing_pos_order(c, kPosAuftrag)) {
            return {
                {"orderId", std::to_string(existing->first)},
                {"orderNumber", existing->second}
            };
        }
    }

    const Defaults& defaults = get_defaults(c);

    if (!get_pool().begin(c)) throw std::runtime_error("failed to begin transaction");

    try {
        g_zahlungsart_cache.clear();

        std::string creation_date = order.value("creationDate", "");
        std::tm order_date_tm = creation_date.empty()
            ? []() {
                  std::tm tm{};
                  auto now = std::chrono::system_clock::now();
                  std::time_t t = std::chrono::system_clock::to_time_t(now);
                  localtime_r(&t, &tm);
                  return tm;
              }()
            : parse_order_date(creation_date);

        auto [kKunde, kKundengruppe] = resolve_customer(c, order, defaults);
        nlohmann::json zahlungsart = resolve_zahlungsart(c, order.value("paymentMethodName", "Bar"));
        std::string cAuftragsNr = next_order_number(c, order_date_tm);

        int active_shop = get_active_shop_id();

        const char* insert_auftrag =
            "DECLARE @t TABLE ([kAuftrag] INT);"
            "INSERT INTO Verkauf.tAuftrag"
            " (cAuftragsNr, dErstellt, nKomplettAusgeliefert, kBenutzer, kKunde, kBenutzerErstellt, nType, fFaktor,"
            " kFirmaHistory, kSprache, cVersandlandWaehrung, fVersandlandWaehrungFaktor, fFinanzierungskosten,"
            " cWaehrung, kPlattform, kShop, cKundenNr, cVersandlandISO, kVersandArt, kZahlungsart, kKundengruppe,"
            " cExterneAuftragsnummer)"
            " OUTPUT inserted.kAuftrag INTO @t"
            " VALUES (?, ?, 0, ?, ?, ?, 1, 1.0,"
            " ?, ?, ?, 1.0, 0.0,"
            " ?, ?, ?, ?, ?, ?, ?, ?,"
            " ?);"
            "SELECT kAuftrag FROM @t";

        std::string shipping_iso = order.value("shippingAddress", nlohmann::json::object()).value("countryIso", "DE");
        std::transform(shipping_iso.begin(), shipping_iso.end(), shipping_iso.begin(),
                       [](unsigned char ch) { return std::toupper(ch); });

        std::string currency_iso = order.value("currencyIso", "EUR");
        std::vector<Param> ps = {
            {ParamType::NVarChar, cAuftragsNr, 0},
            {ParamType::NVarChar, order_date_sql(order_date_tm), 0},
            {ParamType::Int, "", g_config.kBenutzer},
            {ParamType::Int, "", kKunde},
            {ParamType::Int, "", g_config.kBenutzer},
            {ParamType::Int, "", defaults.kFirmaHistory},
            {ParamType::Int, "", g_config.kSprache},
            {ParamType::NVarChar, currency_iso, 0},
            {ParamType::NVarChar, currency_iso, 0},
            {ParamType::Int, "", defaults.kPlattform},
            {ParamType::Int, "", active_shop == 0 ? -1 : active_shop},
            {ParamType::NVarChar, order.value("customerNumber", ""), 0},
            {ParamType::NVarChar, shipping_iso, 0},
            {ParamType::Int, "", defaults.kVersandArt},
            {ParamType::Int, "", json_int(zahlungsart, "kZahlungsart", 0)},
            {ParamType::Int, "", kKundengruppe},
            {ParamType::NVarChar, order.value("externalOrderNumber", ""), 0}
        };
        if (active_shop == 0) {
            ps[11] = Param::null_int();
        }

        ResultSet rs;
        if (!get_pool().execute(c, insert_auftrag, ps, rs) || rs.empty() || rs[0].empty()) {
            throw std::runtime_error("failed to insert Verkauf.tAuftrag");
        }
        int kAuftrag = std::stoi(rs[0][0].str);

        upsert_pos_order_mapping(c, kAuftrag, kPosAuftrag);
        insert_order_address(c, kAuftrag, kKunde, order.value("shippingAddress", nlohmann::json::object()), 0);
        insert_order_address(c, kAuftrag, kKunde, order.value("billingAddress", nlohmann::json::object()), 1);

        std::vector<delivery::DeliveredItem> delivered_items;
        const nlohmann::json& items = order.contains("orderItems") ? order["orderItems"] : nlohmann::json::array();
        for (const auto& item : items) {
            int kAuftragPosition = insert_order_item(c, kAuftrag, item);
            insert_pos_order_position_mapping(c, kAuftragPosition, item.value("externalId", ""));
            if (kAuftragPosition > 0) {
                delivered_items.push_back({kAuftragPosition, to_number(item["quantity"], 1)});
            }
        }

        const nlohmann::json& payments = order.contains("payments") ? order["payments"] : nlohmann::json::array();
        for (const auto& payment : payments) {
            insert_payment(c, kAuftrag, payment, order, order_date_tm);
        }

        if (is_order_delivered(order)) {
            delivery::deliver_order(c, g_config.kBenutzer, kAuftrag, defaults.kVersandArt, delivered_items);
        }

        const char* calc =
            "DECLARE @auftrag_calc Verkauf.TYPE_spAuftragEckdatenBerechnen;"
            "INSERT INTO @auftrag_calc VALUES (?);"
            "EXEC Verkauf.spAuftragEckdatenBerechnen @auftrag = @auftrag_calc";
        std::vector<Param> ps_calc = {{ParamType::Int, "", kAuftrag}};
        get_pool().execute(c, calc, ps_calc, rs);

        if (!get_pool().commit(c)) throw std::runtime_error("failed to commit transaction");

        return { {"orderId", std::to_string(kAuftrag)}, {"orderNumber", cAuftragsNr} };
    } catch (const std::exception&) {
        try { get_pool().rollback(c); } catch (...) {}
        throw;
    }
}

} // namespace order
