/*******************************************************************************
 * Copyright (c) 2026, Jan Koester jan.koester@gmx.net
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 * Neither the name of the <organization> nor the
 *      names of its contributors may be used to endorse or promote products
 *      derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/

#pragma once

#include <string>
#include <vector>
#include <deque>
#include <memory>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <atomic>
#include <utility>
#include <uuidp.h>

#include <paritypp/server.h>
#include <paritypp/client.h>
#include <paritypp/auth.h>

#include "session.h"
#include "backend.h"
#include "authdb.h"

namespace authdb {

    struct ClusterNode {
        std::string address;
        int         port;
    };

    // Wraps a real block_store and mirrors session blocks into a separate session store.
    // When a peer replicates a session to us via the parity server, the server
    // calls store() on this wrapper — if the data deserializes as a valid session,
    // it is also written to session_store_.
    class session_intercepting_store : public paritypp::block_store {
    public:
        session_intercepting_store(std::shared_ptr<paritypp::block_store> inner,
                                   std::shared_ptr<paritypp::memory_block_store> session_store);

        bool store(uint64_t group_id, uint32_t block_index,
                   const uint8_t* data, size_t len) override;
        bool fetch(uint64_t group_id, uint32_t block_index,
                   std::vector<uint8_t>& out) override;
        bool remove_group(uint64_t group_id) override;
        std::vector<uint32_t> list_blocks(uint64_t group_id) override;
        uint32_t total_blocks() override;
        uint32_t total_groups() override;
        std::vector<uint64_t> list_groups() override;

    private:
        std::shared_ptr<paritypp::block_store> inner_;
        std::shared_ptr<paritypp::memory_block_store> session_store_;
    };

    struct ClusterConfig {
        std::vector<ClusterNode> peers;
        std::string              bind_address = "0.0.0.0";
        int                      port = 4433;
        std::string              cert_file;
        std::string              key_file;
        std::string              client_name;
        std::string              client_key;
        std::string              store_path;   // file-backed store directory (empty = memory only)
        size_t                   data_blocks  = 2;  // k: data shards
        size_t                   parity_blocks = 1; // m: parity shards (fault tolerance)
    };

    // --- Session serialization ---

    struct SessionBlock {
        static std::vector<uint8_t> serialize(const SessionData &sess);
        static bool deserialize(const std::vector<uint8_t> &data,
                                uuid::uuid &sid, uuid::uuid &uid, uuid::uuid &did,
                                std::vector<uuid::uuid> &members,
                                std::string &username,
                                std::vector<std::pair<uuid::uuid, bool>> &gpo_results);
    };

    uint64_t sessionGroupId(const uuid::uuid &uid, const uuid::uuid &did);
    uint64_t sidGroupId(const uuid::uuid &sid);

    // --- Data operation replication ---

    enum class OpType : uint8_t {
        Create = 1,
        Edit   = 2,
        Delete = 3
    };

    // Serialized data operation for cluster transport
    // Wire format:
    //   [1B op_type][4B domain_len][domain_bytes]
    //   [16B ruid][4B data_type]
    //   [4B record_count]
    //   for each record:
    //     [255B fieldname][4B storage][4B datasize][datasize bytes]
    struct DataOpBlock {
        static std::vector<uint8_t> serialize(OpType op, const std::string &domain,
                                               const uuid::uuid &uid, int dataType,
                                               const AuthData *records);
        static bool deserialize(const std::vector<uint8_t> &data,
                                OpType &op, std::string &domain,
                                uuid::uuid &uid, int &dataType,
                                std::vector<AuthData::Record> &records);
    };

    // Cluster manager: distributes sessions and data operations across peer nodes
    class Cluster {
    public:
        Cluster();
        ~Cluster();

        // Initialize with config. Must be called before start().
        void init(const ClusterConfig &cfg);

        // Start the cluster server in a background thread
        void start();

        // Stop the cluster
        void stop();

        bool isRunning() const { return running_; }

        // Block until at least k nodes are reachable (up to timeout_s seconds).
        // Returns number of nodes online.
        size_t waitForPeers(int timeout_s = 10);

        // Accessors for backend use
        paritypp::block_store &getStore() { return *store_; }
        std::unique_ptr<paritypp::client> &getClient() { return pclient_; }
        std::timed_mutex &getClientMutex() { return client_mutex_; }

        // Push a session to the cluster (after local addSession)
        void pushSession(const SessionData &sess);

        // Try to fetch a session from the cluster by uid+did
        // Returns true if found, fills out parameters
        bool fetchSession(const uuid::uuid &uid, const uuid::uuid &did,
                          uuid::uuid &sid, std::vector<uuid::uuid> &members,
                          std::string &username,
                          std::vector<std::pair<uuid::uuid, bool>> &gpo_results);

        // Remove a session from the cluster
        void removeSession(const uuid::uuid &uid, const uuid::uuid &did);

        // Remove a session from the cluster by SID
        void removeSessionBySid(const uuid::uuid &session_id);

        // Check if a session already exists in the cluster for uid+did
        bool hasSession(const uuid::uuid &uid, const uuid::uuid &did);

        // Check if a session still exists in the cluster by SID
        bool hasSessionBySid(const uuid::uuid &session_id);

        // Local-only check: fetch session from session_store_ without network calls
        bool fetchSessionLocalBySid(const uuid::uuid &session_id,
                                     uuid::uuid &sid, uuid::uuid &uid, uuid::uuid &did,
                                     std::vector<uuid::uuid> &members,
                                     std::string &username,
                                     std::vector<std::pair<uuid::uuid, bool>> &gpo_results);

        // Fetch a session from the cluster by session ID (SID)
        bool fetchSessionBySid(const uuid::uuid &session_id,
                               uuid::uuid &sid, uuid::uuid &uid, uuid::uuid &did,
                               std::vector<uuid::uuid> &members,
                               std::string &username,
                               std::vector<std::pair<uuid::uuid, bool>> &gpo_results);

        // Replicate a data operation (create/edit/delete) to peers
        void replicateOp(OpType op, const std::string &domain,
                         const uuid::uuid &uid, int dataType,
                         const AuthData *records = nullptr);

        // List all sessions from the dedicated session store (local + imported).
        struct SessionInfo {
            uuid::uuid sid, uid, did;
            std::vector<uuid::uuid> members;
            std::string username;
            std::vector<std::pair<uuid::uuid, bool>> gpo_results;
        };
        void listAllSessions(std::vector<SessionInfo> &out);

        bool isDegraded() const { return degraded_; }

    private:
        ClusterConfig cfg_;
        std::atomic<bool> running_{false};
        std::atomic<bool> degraded_{false};
        std::thread server_thread_;
        std::thread health_monitor_thread_;
        std::thread push_worker_thread_;
        std::mutex monitor_mutex_;
        std::condition_variable monitor_cv_;
        std::mutex push_mutex_;
        std::condition_variable push_cv_;

        struct PendingPush {
            std::vector<uint8_t> data;
            uint64_t sgid;
            uint64_t sid_gid;
            int retries{0};
        };
        std::deque<PendingPush> push_queue_;

        std::shared_ptr<paritypp::block_store> store_;
        std::shared_ptr<paritypp::memory_block_store> session_store_; // sessions only
        std::shared_ptr<paritypp::auth_store> auth_;
        std::unique_ptr<paritypp::server> server_;

        std::unique_ptr<paritypp::client> pclient_;
        std::timed_mutex client_mutex_;

        void server_loop();
        void health_monitor_loop();
        void push_worker_loop();
    };

    // Global cluster instance
    extern Cluster *g_Cluster;
};
