BareGit
#pragma once

#include <chrono>
#include <cstdint>
#include <string>
#include <memory>

#include <mw/error.hpp>

#include "probe_status.h"

/// HTTP configuration; only a direct 2xx response is healthy.
struct HttpEndpoint
{
    /// HTTP or HTTPS URL, after applying any service URL fallback.
    std::string url;
};

/// TCP connection configuration.
struct TcpEndpoint
{
    /// Hostname or IPv4/IPv6 address.
    std::string host;
    /// Destination port, from 1 to 65535.
    std::uint16_t port;
};

/// UDP configuration; silence is OTHER, not proof of an open port.
struct UdpEndpoint
{
    /// Hostname or IPv4/IPv6 address.
    std::string host;
    /// Destination port, from 1 to 65535.
    std::uint16_t port;
    /// Datagram bytes; defaults to an empty datagram.
    std::string payload;
};

/// ICMP echo configuration for Linux ping sockets.
struct IcmpEndpoint
{
    /// Hostname or IPv4/IPv6 address.
    std::string host;
};

/// Measured result ready to be associated with a service and persisted.
struct ProbeResult
{
    /// Outcome of the network check.
    ProbeStatus status;
    /// Probe start time in UTC Unix seconds.
    std::int64_t timestamp;
    /// Elapsed monotonic time in microseconds, including DNS resolution.
    std::int64_t duration_microsecond;
};

/// A task-owned probe for one endpoint check.
class ProbeInterface
{
public:
    /// Release the probe through its interface.
    virtual ~ProbeInterface() = default;

    /// Check the endpoint and measure its result.
    /// Network failures return BAD; silent UDP endpoints return OTHER.
    /// Local execution failures return errors.
    virtual mw::E<ProbeResult> probe() = 0;
};

/// HTTP GET probe owning its endpoint configuration.
class HttpProbe final : public ProbeInterface
{
public:
    /// Perform the configured HTTP GET check.
    mw::E<ProbeResult> probe() override;

private:
    HttpProbe(HttpEndpoint config, std::chrono::seconds timeout);
    friend mw::E<std::unique_ptr<ProbeInterface>> createProbe(
        const HttpEndpoint& config, std::chrono::seconds timeout);

    HttpEndpoint config;
    std::chrono::seconds timeout;
};

/// TCP connection probe owning its endpoint configuration.
class TcpProbe final : public ProbeInterface
{
public:
    /// Perform the configured TCP connection check.
    mw::E<ProbeResult> probe() override;

private:
    TcpProbe(TcpEndpoint config, std::chrono::seconds timeout);
    friend mw::E<std::unique_ptr<ProbeInterface>> createProbe(
        const TcpEndpoint& config, std::chrono::seconds timeout);

    TcpEndpoint config;
    std::chrono::seconds timeout;
};

/// UDP request/reply probe owning its endpoint configuration.
class UdpProbe final : public ProbeInterface
{
public:
    /// Perform the configured UDP request/reply check.
    mw::E<ProbeResult> probe() override;

private:
    UdpProbe(UdpEndpoint config, std::chrono::seconds timeout);
    friend mw::E<std::unique_ptr<ProbeInterface>> createProbe(
        const UdpEndpoint& config, std::chrono::seconds timeout);

    UdpEndpoint config;
    std::chrono::seconds timeout;
};

/// ICMP echo probe owning its endpoint configuration.
class IcmpProbe final : public ProbeInterface
{
public:
    /// Perform the configured ICMP echo check.
    mw::E<ProbeResult> probe() override;

private:
    IcmpProbe(IcmpEndpoint config, std::chrono::seconds timeout);
    friend mw::E<std::unique_ptr<ProbeInterface>> createProbe(
        const IcmpEndpoint& config, std::chrono::seconds timeout);

    IcmpEndpoint config;
    std::chrono::seconds timeout;
};

/// Create a HTTP probe with a positive timeout for DNS and network I/O.
mw::E<std::unique_ptr<ProbeInterface>> createProbe(
    const HttpEndpoint& config,
    std::chrono::seconds timeout = std::chrono::seconds(5));

/// Create a TCP probe with a positive timeout for DNS and network I/O.
mw::E<std::unique_ptr<ProbeInterface>> createProbe(
    const TcpEndpoint& config,
    std::chrono::seconds timeout = std::chrono::seconds(5));

/// Create a UDP probe with a positive timeout for DNS and network I/O.
mw::E<std::unique_ptr<ProbeInterface>> createProbe(
    const UdpEndpoint& config,
    std::chrono::seconds timeout = std::chrono::seconds(5));

/// Create a ICMP probe with a positive timeout for DNS and network I/O.
mw::E<std::unique_ptr<ProbeInterface>> createProbe(
    const IcmpEndpoint& config,
    std::chrono::seconds timeout = std::chrono::seconds(5));