#include "database.h"
#include <pqxx/pqxx>
#include <vector>
#include <unordered_map>
#include <string>
#include <iostream>

Database::Database(const std::string &conninfo)
{
    try {
        connection_ = std::make_unique<pqxx::connection>(conninfo);
        if (!connection_->is_open()) {
            throw std::runtime_error("Konnte DB-Verbindung nicht öffnen!");
        }
    } catch (const std::exception &e) {
        std::cerr << "[Database] Fehler beim Verbinden: " << e.what() << std::endl;


        throw;
    }
}

std::vector<std::map<std::string, std::string>> 
Database::query(const std::string &sql)
{
    std::vector<std::map<std::string, std::string>> rows;
    try {
        pqxx::work txn(*connection_);
        pqxx::result r = txn.exec(sql);
        txn.commit();
        
        // Pre-allocate memory for better performance
        rows.reserve(r.size());
        
        for (const auto& row : r) {
            std::map<std::string, std::string> oneRow;
            
            for (auto f = 0u; f < row.size(); f++) {
                const std::string colName = r.column_name(f);
                const char* value = row[f].c_str();
                oneRow.emplace(colName, value ? value : "");
            }
            rows.emplace_back(std::move(oneRow));
        }
    } catch (const std::exception &ex) {
        std::cerr << "[Database] query-Fehler: " << ex.what() << "\nSQL: " << sql << std::endl;
    }
    return rows;
}

void Database::prepare(const std::string &stmtName, const std::string &sql)
{
    try {
        // Versuche zuerst, das alte Statement zu entfernen, falls es existiert
        try {
            remove(stmtName);
        } catch (...) {
            // Ignoriere Fehler beim Entfernen - das Statement existiert möglicherweise nicht
        }
        
        // Erstelle das neue Statement
        pqxx::work txn(*connection_);
        txn.conn().prepare(stmtName, sql);
        txn.commit();
    } catch (const std::exception &ex) {
        std::cerr << "[Database] prepare-Fehler: " << ex.what() 
                  << "\nSQL: " << sql << std::endl;
    }
}

Database::FieldList Database::execute(const std::string& stmtName,
                                      const std::vector<std::string>& params)
{
    try {
        pqxx::work txn(*connection_);

        pqxx::result res;
        if (params.empty()) {
            res = txn.exec_prepared(stmtName);
        } else {
            // Kompatibilität für libpqxx 6.x (Ubuntu 22) und 7.x (OpenSUSE Tumbleweed)
            #if PQXX_VERSION_MAJOR >= 7
                pqxx::params p;
                for (const auto& v : params) p.append(v);
                res = txn.exec_prepared(stmtName, p);
            #else
                // Für libpqxx 6.x - verwende exec_params mit variadic template
                if (params.size() == 0) {
                    res = txn.exec_prepared(stmtName);
                } else if (params.size() == 1) {
                    res = txn.exec_prepared(stmtName, params[0]);
                } else if (params.size() == 2) {
                    res = txn.exec_prepared(stmtName, params[0], params[1]);
                } else if (params.size() == 3) {
                    res = txn.exec_prepared(stmtName, params[0], params[1], params[2]);
                } else if (params.size() == 4) {
                    res = txn.exec_prepared(stmtName, params[0], params[1], params[2], params[3]);
                } else if (params.size() == 5) {
                    res = txn.exec_prepared(stmtName, params[0], params[1], params[2], params[3], params[4]);
                } else {
                    // Für mehr als 5 Parameter, verwende exec_params mit einzelnen Parametern
                    std::string sql = "EXECUTE " + stmtName;
                    if (!params.empty()) {
                        sql += "(";
                        for (size_t i = 0; i < params.size(); ++i) {
                            if (i > 0) sql += ", ";
                            sql += "$" + std::to_string(i + 1);
                        }
                        sql += ")";
                    }
                    
                    // Konvertiere vector zu einzelnen Parametern für exec_params
                    if (params.size() == 6) {
                        res = txn.exec_params(sql, params[0], params[1], params[2], params[3], params[4], params[5]);
                    } else if (params.size() == 7) {
                        res = txn.exec_params(sql, params[0], params[1], params[2], params[3], params[4], params[5], params[6]);
                    } else if (params.size() == 8) {
                        res = txn.exec_params(sql, params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7]);
                    } else if (params.size() == 9) {
                        res = txn.exec_params(sql, params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7], params[8]);
                    } else if (params.size() == 10) {
                        res = txn.exec_params(sql, params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7], params[8], params[9]);
                    } else {
                        // Für noch mehr Parameter, verwende eine einfachere Lösung
                        throw std::runtime_error("Zu viele Parameter für prepared statement: " + std::to_string(params.size()));
                    }
                }
            #endif
        }

        FieldList out;
        out.reserve(res.size());
        for (const auto& row : res) {
            std::unordered_map<std::string, std::string> m;
            m.reserve(row.size()); // Pre-allocate for better performance
            
            for (const auto& f : row) {
                // Use string_view for better performance (C++17+)
                const std::string_view name = f.name();
                const char* value = f.c_str();
                m.emplace(name, f.is_null() ? std::string{} : std::string(value));
            }
            out.emplace_back(std::move(m));
        }

        txn.commit();
        return out;
    } catch (const std::exception& e) {
        std::cerr << "[Database] execute-Fehler: " << e.what()
                  << "\n\nStatement: " << stmtName << std::endl;
        return {};
    }
}

void Database::remove(const std::string &stmtName) {
    pqxx::work txn(*connection_);
    txn.conn().unprepare(stmtName);
    txn.commit();
}

bool Database::isValid() const {
    try {
        if (!connection_ || !connection_->is_open()) {
            return false;
        }
        pqxx::work txn(*connection_);
        txn.exec("SELECT 1"); // Einfacher Ping
        txn.commit();
        return true;
    } catch (const std::exception &ex) {
        std::cerr << "[Database] Verbindung ungültig: " << ex.what() << "\n";
        return false;
    }
}
