#pragma once

#include <memory>
#include <unordered_map>
#include <unistd.h>

#include <tl/expected.hpp>

#include "quicr/QuicrAddress.hpp"
#include "quicr/QuicrError.hpp"
#include "quicr/QuicrConnection.hpp"


namespace tw::net::quicr {

class QuicrConnection;
class QuicrConnectionListener;

class QuicrEndpoint {
    int32_t m_socket_fd;
    sa_family_t m_family;
    std::unordered_map<uint64_t, std::shared_ptr<QuicrConnection>> m_connections;

    std::vector<std::byte> m_inbound_buffer;

    QuicrConnectionListener* m_new_connection_handler;

    void process_datagram(std::span<std::byte> datagram, QuicrAddress from);

    QuicrEndpoint(int socket_fd, sa_family_t family);

public:
    QuicrEndpoint(const QuicrEndpoint&) = delete;
    QuicrEndpoint& operator=(const QuicrEndpoint&) = delete;
    QuicrEndpoint(QuicrEndpoint&&) = delete;
    QuicrEndpoint& operator=(QuicrEndpoint&&) = delete;

    ~QuicrEndpoint() {
        ::close(m_socket_fd);
        m_socket_fd = -1;
    }

    std::vector<std::pair<uint64_t, std::shared_ptr<QuicrConnection>>> clients() const {
        std::vector<std::pair<uint64_t, std::shared_ptr<QuicrConnection>>> result;
        for (const auto& [id, connection] : m_connections) {
            result.emplace_back(id, connection);
        }
        return result;
    }

    /**
     * The family the socket was opened with. Addresses have to be resolved
     * into it before they can be sent to.
     */
    sa_family_t family() const { return m_family; }

    static tl::expected<std::unique_ptr<QuicrEndpoint>, QuicrError> create();

    /**
     * Creates the QUICr endpoint and binds it to a port.
     */
    static tl::expected<std::unique_ptr<QuicrEndpoint>, QuicrError> create_and_bind(int16_t port);

    void assign_listener(QuicrConnectionListener* listener) {
        m_new_connection_handler = listener;
    }

    tl::expected<void, QuicrError> bind(int port);

    tl::expected<QuicrConnection*, QuicrError> connect(QuicrAddress address);

    tl::expected<size_t, QuicrError> send_to(std::span<std::byte> data, QuicrAddress to);

    tl::expected<size_t, QuicrError> read_from_into(std::span<std::byte> data, QuicrAddress* out_from);

    void poll();
};

}
