#include "cluster.h"

namespace mediadb {

Cluster* g_Cluster = nullptr;

void Cluster::init(const ClusterConfig& cfg) {
    cfg_ = cfg;

    if (!cfg_.store_path.empty()) {
        store_ = std::make_shared<paritypp::file_block_store>(cfg_.store_path);
    } else {
        store_ = std::make_shared<paritypp::memory_block_store>();
    }

    auth_ = std::make_shared<paritypp::auth_store>();
    if (!cfg_.client_name.empty()) {
        auth_->add_client(cfg_.client_name, cfg_.client_key);
    }

    paritypp::server::config scfg;
    scfg.bind_address = cfg_.bind_address;
    scfg.port = cfg_.port;
    scfg.cert_file = cfg_.cert_file;
    scfg.key_file = cfg_.key_file;
    scfg.max_connections = 64;
    scfg.require_auth = !cfg_.client_name.empty();

    server_ = std::make_unique<paritypp::server>(scfg, store_, auth_);

    size_t peer_count = 0;
    if (!cfg_.peers.empty()) {
        std::vector<std::string> local_addrs;
        {
            char hostname[256] = {};
            gethostname(hostname, sizeof(hostname));
            local_addrs.emplace_back(hostname);
        }
#ifndef _WIN32
        {
            struct ifaddrs* ifaddr = nullptr;
            if (getifaddrs(&ifaddr) == 0) {
                for (struct ifaddrs* ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
                    if (!ifa->ifa_addr) continue;
                    char buf[INET6_ADDRSTRLEN] = {};
                    if (ifa->ifa_addr->sa_family == AF_INET) {
                        inet_ntop(AF_INET,
                                  &reinterpret_cast<struct sockaddr_in*>(ifa->ifa_addr)->sin_addr,
                                  buf, sizeof(buf));
                        local_addrs.emplace_back(buf);
                    } else if (ifa->ifa_addr->sa_family == AF_INET6) {
                        inet_ntop(AF_INET6,
                                  &reinterpret_cast<struct sockaddr_in6*>(ifa->ifa_addr)->sin6_addr,
                                  buf, sizeof(buf));
                        local_addrs.emplace_back(buf);
                    }
                }
                freeifaddrs(ifaddr);
            }
        }
#endif
        std::vector<paritypp::client::node_info> nodes;
        for (const auto& peer : cfg_.peers) {
            bool is_self = false;
            if (peer.port == cfg_.port) {
                for (const auto& addr : local_addrs) {
                    if (peer.address == addr) { is_self = true; break; }
                }
            }
            if (is_self) continue;
            nodes.push_back({peer.address, peer.port});
        }
        peer_count = nodes.size();

        paritypp::client::credentials pcreds;
        if (!cfg_.client_name.empty()) {
            pcreds.client_name = cfg_.client_name;
            pcreds.key.assign(cfg_.client_key.begin(), cfg_.client_key.end());
        }

        if (!nodes.empty()) {
            pclient_ = std::make_unique<paritypp::client>(
                cfg_.data_blocks, cfg_.parity_blocks, nodes, pcreds);
        }
    }

    server_->set_peer_status_callback([peer_count]() -> std::pair<uint32_t, uint32_t> {
        return {static_cast<uint32_t>(peer_count), static_cast<uint32_t>(peer_count)};
    });
}

void Cluster::start() {
    if (running_) return;
    running_ = true;
    server_thread_ = std::thread(&Cluster::server_loop, this);
}

void Cluster::stop() {
    if (!running_) return;
    running_ = false;
    if (server_) server_->stop();
    if (server_thread_.joinable()) server_thread_.join();
}

std::vector<Cluster::NodeGroupInfo> Cluster::list_peer_groups() {
    std::vector<NodeGroupInfo> result;
    if (!pclient_) return result;
    std::unique_lock<std::timed_mutex> lock(client_mutex_, std::chrono::seconds(5));
    if (!lock.owns_lock()) return result;
    size_t idx = 0;
    for (const auto& peer : cfg_.peers) {
        NodeGroupInfo ngi;
        ngi.index = idx;
        ngi.address = peer.address;
        ngi.port = peer.port;
        ngi.online = false;
        if (pclient_->list_groups_on_node(idx, ngi.groups)) {
            ngi.online = true;
        }
        result.push_back(std::move(ngi));
        ++idx;
    }
    return result;
}

void Cluster::replicate(const std::string& key, const uint8_t* data, size_t len) {
    uint64_t gid = cluster_group_id(key);
    store_->store(gid, 0, data, len);
    if (!pclient_) return;
    try {
        std::unique_lock<std::timed_mutex> lock(client_mutex_, std::chrono::seconds(5));
        if (!lock.owns_lock()) throw std::runtime_error("cluster client lock timeout");
        pclient_->replicate_to_peers(gid, 0, data, len);
    } catch (const std::exception& e) {
        std::cerr << "Cluster replicate failed (" << key << "): " << e.what() << std::endl;
    }
}

bool Cluster::fetch(const std::string& key, std::vector<uint8_t>& out) {
    uint64_t gid = cluster_group_id(key);
    if (store_->fetch(gid, 0, out) && !out.empty()) return true;
    if (!pclient_) return false;
    try {
        std::unique_lock<std::timed_mutex> lock(client_mutex_, std::chrono::seconds(5));
        if (!lock.owns_lock()) throw std::runtime_error("cluster client lock timeout");
        if (pclient_->fetch_from_peers(gid, 0, out) && !out.empty()) {
            store_->store(gid, 0, out.data(), out.size());
            return true;
        }
    } catch (...) {}
    return false;
}

bool Cluster::fetch_from_peers(const std::string& key, std::vector<uint8_t>& out) {
    if (!pclient_) return false;
    uint64_t gid = cluster_group_id(key);
    try {
        std::unique_lock<std::timed_mutex> lock(client_mutex_, std::chrono::seconds(5));
        if (!lock.owns_lock()) throw std::runtime_error("cluster client lock timeout");
        if (pclient_->fetch_from_peers(gid, 0, out) && !out.empty()) {
            store_->store(gid, 0, out.data(), out.size());
            return true;
        }
    } catch (const std::exception& e) {
        std::cerr << "Cluster fetch_from_peers failed (" << key << "): " << e.what() << std::endl;
    }
    return false;
}

void Cluster::remove(const std::string& key) {
    uint64_t gid = cluster_group_id(key);
    store_->remove_group(gid);
    if (!pclient_) return;
    try {
        std::unique_lock<std::timed_mutex> lock(client_mutex_, std::chrono::seconds(5));
        if (!lock.owns_lock()) throw std::runtime_error("cluster client lock timeout");
        pclient_->remove(gid);
    } catch (...) {}
}

void Cluster::server_loop() {
    try {
        server_->run();
    } catch (const std::exception& e) {
        std::cerr << "Cluster server error: " << e.what() << std::endl;
    }
    running_ = false;
}

} // namespace mediadb
