/**
 * @file ibc.hpp
 * @brief Synor IBC SDK for C++
 *
 * Inter-Blockchain Communication (IBC) protocol for cross-chain interoperability.
 */

#ifndef SYNOR_IBC_HPP
#define SYNOR_IBC_HPP

#include <string>
#include <vector>
#include <optional>
#include <memory>
#include <cstdint>
#include <future>
#include <nlohmann/json.hpp>

namespace synor {
namespace ibc {

using json = nlohmann::json;

/**
 * IBC Height representation
 */
struct Height {
    uint64_t revision_number = 0;
    uint64_t revision_height = 1;

    bool is_zero() const {
        return revision_number == 0 && revision_height == 0;
    }

    Height increment() const {
        return Height{revision_number, revision_height + 1};
    }

    static Height from_json(const json& j) {
        return Height{
            j.value("revision_number", uint64_t(0)),
            j.value("revision_height", uint64_t(1))
        };
    }

    json to_json() const {
        return {
            {"revision_number", revision_number},
            {"revision_height", revision_height}
        };
    }
};

/**
 * Light client types
 */
enum class ClientType {
    Tendermint,
    SoloMachine,
    Localhost,
    Wasm
};

inline std::string to_string(ClientType type) {
    switch (type) {
        case ClientType::Tendermint: return "tendermint";
        case ClientType::SoloMachine: return "solo_machine";
        case ClientType::Localhost: return "localhost";
        case ClientType::Wasm: return "wasm";
    }
    return "unknown";
}

/**
 * Trust level configuration
 */
struct TrustLevel {
    int numerator = 1;
    int denominator = 3;

    static TrustLevel from_json(const json& j) {
        return TrustLevel{
            j.value("numerator", 1),
            j.value("denominator", 3)
        };
    }

    json to_json() const {
        return {{"numerator", numerator}, {"denominator", denominator}};
    }
};

/**
 * Light client state
 */
struct ClientState {
    std::string chain_id;
    TrustLevel trust_level;
    uint64_t trusting_period;
    uint64_t unbonding_period;
    uint64_t max_clock_drift;
    Height latest_height;
    std::optional<Height> frozen_height;

    static ClientState from_json(const json& j) {
        ClientState state;
        state.chain_id = j.at("chain_id").get<std::string>();
        state.trust_level = TrustLevel::from_json(j.at("trust_level"));
        state.trusting_period = j.at("trusting_period").get<uint64_t>();
        state.unbonding_period = j.at("unbonding_period").get<uint64_t>();
        state.max_clock_drift = j.at("max_clock_drift").get<uint64_t>();
        state.latest_height = Height::from_json(j.at("latest_height"));
        if (j.contains("frozen_height") && !j["frozen_height"].is_null()) {
            state.frozen_height = Height::from_json(j["frozen_height"]);
        }
        return state;
    }

    json to_json() const {
        json j = {
            {"chain_id", chain_id},
            {"trust_level", trust_level.to_json()},
            {"trusting_period", trusting_period},
            {"unbonding_period", unbonding_period},
            {"max_clock_drift", max_clock_drift},
            {"latest_height", latest_height.to_json()}
        };
        if (frozen_height) {
            j["frozen_height"] = frozen_height->to_json();
        }
        return j;
    }
};

/**
 * Connection state
 */
enum class ConnectionState {
    Uninitialized,
    Init,
    TryOpen,
    Open
};

/**
 * Channel ordering
 */
enum class ChannelOrder {
    Unordered,
    Ordered
};

inline std::string to_string(ChannelOrder order) {
    return order == ChannelOrder::Ordered ? "ordered" : "unordered";
}

/**
 * Channel state
 */
enum class ChannelState {
    Uninitialized,
    Init,
    TryOpen,
    Open,
    Closed
};

/**
 * Timeout information
 */
struct Timeout {
    Height height;
    uint64_t timestamp = 0;

    static Timeout from_height(uint64_t h) {
        return Timeout{{0, h}, 0};
    }

    static Timeout from_timestamp(uint64_t ts) {
        return Timeout{{}, ts};
    }

    json to_json() const {
        return {
            {"height", height.to_json()},
            {"timestamp", std::to_string(timestamp)}
        };
    }
};

/**
 * Transfer packet data (ICS-20)
 */
struct FungibleTokenPacketData {
    std::string denom;
    std::string amount;
    std::string sender;
    std::string receiver;
    std::string memo;

    bool is_native() const {
        return denom.find('/') == std::string::npos;
    }

    static FungibleTokenPacketData from_json(const json& j) {
        return FungibleTokenPacketData{
            j.at("denom").get<std::string>(),
            j.at("amount").get<std::string>(),
            j.at("sender").get<std::string>(),
            j.at("receiver").get<std::string>(),
            j.value("memo", "")
        };
    }

    json to_json() const {
        return {
            {"denom", denom},
            {"amount", amount},
            {"sender", sender},
            {"receiver", receiver},
            {"memo", memo}
        };
    }
};

/**
 * Swap state
 */
enum class SwapState {
    Pending,
    Locked,
    Completed,
    Refunded,
    Expired,
    Cancelled
};

inline SwapState swap_state_from_string(const std::string& s) {
    if (s == "pending") return SwapState::Pending;
    if (s == "locked") return SwapState::Locked;
    if (s == "completed") return SwapState::Completed;
    if (s == "refunded") return SwapState::Refunded;
    if (s == "expired") return SwapState::Expired;
    return SwapState::Cancelled;
}

/**
 * Atomic swap
 */
struct AtomicSwap {
    std::string swap_id;
    SwapState state;
    json initiator_htlc;
    std::optional<json> responder_htlc;

    static AtomicSwap from_json(const json& j) {
        AtomicSwap swap;
        swap.swap_id = j.at("swap_id").at("id").get<std::string>();
        swap.state = swap_state_from_string(j.at("state").get<std::string>());
        swap.initiator_htlc = j.at("initiator_htlc");
        if (j.contains("responder_htlc") && !j["responder_htlc"].is_null()) {
            swap.responder_htlc = j["responder_htlc"];
        }
        return swap;
    }
};

/**
 * IBC SDK configuration
 */
struct IbcConfig {
    std::string api_key;
    std::string endpoint = "https://ibc.synor.io/v1";
    std::string ws_endpoint = "wss://ibc.synor.io/v1/ws";
    int timeout = 30;
    int retries = 3;
    std::string chain_id = "synor-1";
    bool debug = false;
};

/**
 * IBC exception
 */
class IbcException : public std::exception {
public:
    IbcException(const std::string& message,
                 const std::string& code = "",
                 int status = 0)
        : message_(message), code_(code), status_(status) {}

    const char* what() const noexcept override { return message_.c_str(); }
    const std::string& code() const { return code_; }
    int status() const { return status_; }

private:
    std::string message_;
    std::string code_;
    int status_;
};

// Forward declarations
class SynorIbc;

/**
 * Light client sub-client
 */
class LightClientClient {
public:
    explicit LightClientClient(SynorIbc& ibc) : ibc_(ibc) {}

    std::future<std::string> create(ClientType client_type,
                                     const ClientState& client_state,
                                     const json& consensus_state);

    std::future<ClientState> get_state(const std::string& client_id);

    std::future<std::vector<json>> list();

private:
    SynorIbc& ibc_;
};

/**
 * Connections sub-client
 */
class ConnectionsClient {
public:
    explicit ConnectionsClient(SynorIbc& ibc) : ibc_(ibc) {}

    std::future<std::string> open_init(const std::string& client_id,
                                        const std::string& counterparty_client_id);

    std::future<json> get(const std::string& connection_id);

    std::future<std::vector<json>> list();

private:
    SynorIbc& ibc_;
};

/**
 * Channels sub-client
 */
class ChannelsClient {
public:
    explicit ChannelsClient(SynorIbc& ibc) : ibc_(ibc) {}

    std::future<void> bind_port(const std::string& port_id, const std::string& module);

    std::future<std::string> open_init(const std::string& port_id,
                                        ChannelOrder ordering,
                                        const std::string& connection_id,
                                        const std::string& counterparty_port,
                                        const std::string& version);

    std::future<json> get(const std::string& port_id, const std::string& channel_id);

    std::future<std::vector<json>> list();

private:
    SynorIbc& ibc_;
};

/**
 * Transfer sub-client (ICS-20)
 */
class TransferClient {
public:
    explicit TransferClient(SynorIbc& ibc) : ibc_(ibc) {}

    std::future<json> transfer(const std::string& source_port,
                                const std::string& source_channel,
                                const std::string& denom,
                                const std::string& amount,
                                const std::string& sender,
                                const std::string& receiver,
                                std::optional<Timeout> timeout = std::nullopt,
                                std::optional<std::string> memo = std::nullopt);

    std::future<json> get_denom_trace(const std::string& ibc_denom);

private:
    SynorIbc& ibc_;
};

/**
 * Swaps sub-client (HTLC)
 */
class SwapsClient {
public:
    explicit SwapsClient(SynorIbc& ibc) : ibc_(ibc) {}

    std::future<json> initiate(const std::string& responder,
                                const json& initiator_asset,
                                const json& responder_asset);

    std::future<void> lock(const std::string& swap_id);

    std::future<json> respond(const std::string& swap_id, const json& asset);

    std::future<json> claim(const std::string& swap_id,
                             const std::vector<uint8_t>& secret);

    std::future<json> refund(const std::string& swap_id);

    std::future<AtomicSwap> get(const std::string& swap_id);

    std::future<std::vector<AtomicSwap>> list_active();

private:
    SynorIbc& ibc_;
};

/**
 * Main IBC client
 */
class SynorIbc {
public:
    explicit SynorIbc(const IbcConfig& config);
    ~SynorIbc();

    // Non-copyable
    SynorIbc(const SynorIbc&) = delete;
    SynorIbc& operator=(const SynorIbc&) = delete;

    // Movable
    SynorIbc(SynorIbc&&) noexcept;
    SynorIbc& operator=(SynorIbc&&) noexcept;

    const std::string& chain_id() const { return config_.chain_id; }

    std::future<json> get_chain_info();
    std::future<Height> get_height();
    std::future<bool> health_check();

    void close();
    bool is_closed() const { return closed_; }

    LightClientClient& clients() { return *clients_; }
    ConnectionsClient& connections() { return *connections_; }
    ChannelsClient& channels() { return *channels_; }
    TransferClient& transfer() { return *transfer_; }
    SwapsClient& swaps() { return *swaps_; }

    // Internal HTTP methods (used by sub-clients)
    std::future<json> get(const std::string& path);
    std::future<json> post(const std::string& path, const json& body);

private:
    void check_closed() const;

    IbcConfig config_;
    bool closed_ = false;

    std::unique_ptr<LightClientClient> clients_;
    std::unique_ptr<ConnectionsClient> connections_;
    std::unique_ptr<ChannelsClient> channels_;
    std::unique_ptr<TransferClient> transfer_;
    std::unique_ptr<SwapsClient> swaps_;

    // HTTP client implementation (pimpl)
    class Impl;
    std::unique_ptr<Impl> impl_;
};

} // namespace ibc
} // namespace synor

#endif // SYNOR_IBC_HPP
