#include "types.h"
#include "cluster.h"
#include "backend.h"
#include "auth.h"
#include "preview.h"
#include "app.h"
#include "server.h"

#include <iostream>
#include <memory>
#include <string>

#include <cmdplus/cmdplus.h>
#include <confplus/conf.h>

static std::string conf_string(confplus::Config& cfg, const std::string& key, const std::string& fallback) {
    try {
        auto* kd = cfg.getKey(key);
        if (kd == nullptr) {
            return fallback;
        }
        const std::string& val = cfg.getValue(kd, 0);
        return val.empty() ? fallback : val;
    } catch (...) {
        return fallback;
    }
}

static int conf_int(confplus::Config& cfg, const std::string& key, int fallback) {
    try {
        auto* kd = cfg.getKey(key);
        if (kd == nullptr) {
            return fallback;
        }
        return cfg.getIntValue(kd, 0);
    } catch (...) {
        return fallback;
    }
}

int main(int argc, char* argv[]) {
    auto& cmd = cmdplus::CmdController::getInstance();
    cmd.registerCmd("config",     'c', false, "yaml:/etc/mediadb/config.yaml", "Path to config file (e.g. yaml:/path/to/config.yaml)");
    cmd.registerCmd("foreground", 'f', false, "", "Run in foreground (do not daemonize)");
    cmd.registerCmd("help",       'h', false, "", "Show this help message");
    cmd.parseCmd(argc, argv);

    if (cmd.getCmdbyKey("help")->getFound()) {
        cmd.printHelp();
        return 0;
    }

    const std::string config_path = cmd.getCmdbyKey("config")->getValue();

    confplus::Config cfg(config_path);

    const std::string authdb_url    = conf_string(cfg, "/MEDIADB/AUTHDB/URL",           "http://127.0.0.1:9090");
    const std::string authdb_client = conf_string(cfg, "/MEDIADB/AUTHDB/CLIENTNAME",   "mediadb");
    const std::string authdb_secret = conf_string(cfg, "/MEDIADB/AUTHDB/CLIENTSECRET", "");
    const std::string addr           = conf_string(cfg, "/MEDIADB/HTTP/ADDRESS",             "0.0.0.0");
    const int         port           = conf_int(cfg,    "/MEDIADB/HTTP/PORT",                8080);
    const int         max_connections = conf_int(cfg,   "/MEDIADB/HTTP/MAXCON",               1024);
    const std::string cert_path      = conf_string(cfg, "/MEDIADB/HTTP/CERT",                "");
    const std::string key_path       = conf_string(cfg, "/MEDIADB/HTTP/KEY",                 "");
    const std::string db_dir         = conf_string(cfg, "/MEDIADB/DB/DIR",                   "/var/lib/mediadb/data");
    const std::string html_dir       = conf_string(cfg, "/MEDIADB/HTTP/HTMLDIR",              "/usr/share/mediadb/html");
    const int         cache_size_mb  = conf_int(cfg,    "/MEDIADB/CACHE/SIZE_MB",              256);
    const int         render_threads = conf_int(cfg,    "/MEDIADB/PREVIEW/THREADS",            0);

    const bool foreground = cmd.getCmdbyKey("foreground")->getFound();
    const std::string run_user = conf_string(cfg, "/MEDIADB/RUN_USER", "mediadb");

    // ---- Cluster config ----
    mediadb::Cluster cluster;
    const std::string cluster_enable = conf_string(cfg, "/MEDIADB/CLUSTER/ENABLE", "false");
    if (cluster_enable == "true") {
        mediadb::ClusterConfig ccfg;
        ccfg.enabled      = true;
        ccfg.bind_address = conf_string(cfg, "/MEDIADB/CLUSTER/BIND", "0.0.0.0");
        ccfg.port         = conf_int(cfg, "/MEDIADB/CLUSTER/PORT", 4433);
        ccfg.cert_file    = cert_path;
        ccfg.key_file     = key_path;
        ccfg.client_name  = conf_string(cfg, "/MEDIADB/CLUSTER/AUTH_NAME", "");
        ccfg.client_key   = conf_string(cfg, "/MEDIADB/CLUSTER/AUTH_KEY", "");
        ccfg.store_path   = conf_string(cfg, "/MEDIADB/CLUSTER/PATH", "");

        int db = conf_int(cfg, "/MEDIADB/CLUSTER/DATA_BLOCKS", 2);
        int pb = conf_int(cfg, "/MEDIADB/CLUSTER/PARITY_BLOCKS", 1);
        ccfg.data_blocks   = static_cast<size_t>(db);
        ccfg.parity_blocks = static_cast<size_t>(pb);

        auto* peersKey = cfg.getKey("/MEDIADB/CLUSTER/PEERS");
        if (peersKey) {
            for (int pi = 0; ; pi++) {
                try {
                    std::string peerStr = cfg.getValue(peersKey, pi);
                    if (peerStr.empty()) break;
                    auto colon = peerStr.rfind(':');
                    if (colon != std::string::npos) {
                        mediadb::ClusterNode node;
                        node.address = peerStr.substr(0, colon);
                        node.port = std::stoi(peerStr.substr(colon + 1));
                        ccfg.peers.push_back(node);
                    }
                } catch (...) { break; }
            }
        }

        cluster.init(ccfg);
        mediadb::g_Cluster = &cluster;
    }

    // ---- Shared LRU cache ----
    mediadb::BlobCache cache(static_cast<std::size_t>(cache_size_mb) * 1024ULL * 1024ULL,
                             mediadb::blob_value_size);

    // ---- Create backend: local or cluster ----
    std::unique_ptr<mediadb::MediaBackendApi> backend;
    if (cluster_enable == "true") {
        backend = std::make_unique<mediadb::ClusterMediaBackend>(cluster, cache);
        std::cerr << "Using cluster backend (paritypp storage only)" << std::endl;
    } else {
        backend = std::make_unique<mediadb::BinDb>(db_dir);
        std::cerr << "Using local backend" << std::endl;
    }

    mediadb::App app(*backend, authdb_url, authdb_client, authdb_secret, html_dir, cache,
                      static_cast<std::size_t>(render_threads));
    mediadb::HttpServer server(app, addr, port, max_connections, cert_path, key_path,
                               foreground, run_user, db_dir);

    // Start cluster AFTER fork so the server thread survives in the child process
    if (cluster_enable == "true") {
        auto* cluster_backend = dynamic_cast<mediadb::ClusterMediaBackend*>(backend.get());
        server.post_fork_callback = [&cluster, cluster_backend]() {
            cluster.start();
            // Wait for peers before syncing — prevents empty index on startup
            size_t online = cluster.waitForPeers(120);
            if (online == 0) {
                std::cerr << "[mediadb] CRITICAL: no peers reachable, starting without cluster data\n";
            }
            if (cluster_backend) cluster_backend->start_sync();
        };
    }

    int rc = server.run();

    // Shutdown cluster (backend destructor stops sync thread)
    backend.reset();
    mediadb::g_Cluster = nullptr;
    cluster.stop();

    return rc;
}
