/*
 * SPDX-FileCopyrightText: 2024 Copyright contributors to the cppTango project
 *
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */
#include "catch2_common.h"

#include <tango/common/telemetry/macros.h>

namespace
{
constexpr double SERVER_VALUE = 8.888;
}

template <class Base>
class TelemetryDS : public Base
{
  public:
    using Base::Base;

    void init_device() override { }

    void read_attribute(Tango::Attribute &att)
    {
        auto span =
            TANGO_TELEMETRY_SPAN(TELEMETRY_CURRENT_FUNCTION, {{"myKey", "myValue"}, {"tango.telemetry.topic", "user"}});
        auto scope = TANGO_TELEMETRY_SCOPE(span);

        DEBUG_STREAM << "read_attribute DEBUG_STREAM" << std::endl;
        std::cout << std::flush; // Flush to ensure output is available to the test

        attr_dq_double = SERVER_VALUE;
        att.set_value_date_quality(&attr_dq_double, std::chrono::system_clock::now(), Tango::ATTR_VALID);
    }

    static void attribute_factory(std::vector<Tango::Attr *> &attrs)
    {
        attrs.push_back(new TangoTest::AutoAttr<&TelemetryDS::read_attribute>("attr_dq_db", Tango::DEV_DOUBLE));
    }

  private:
    Tango::DevDouble attr_dq_double;
};

TANGO_TEST_AUTO_DEV_TMPL_INSTANTIATE(TelemetryDS, 3)

SCENARIO("Telemetry traces are outputted")
{
    int idlver = GENERATE(TangoTest::idlversion(3));
    GIVEN("a device proxy to a simple IDLv" << idlver << " device")
    {
        std::vector<std::string> env{"TANGO_TELEMETRY_ENABLE=on",
                                     "TANGO_TELEMETRY_TYPES=tracing",
                                     "TANGO_TELEMETRY_TRACING_EXPORTERS=console,console",
                                     "TANGO_TELEMETRY_TRACING_ENDPOINTS=cout,cerr"};
        TangoTest::Context ctx{"telemetry", "TelemetryDS", idlver, env};
        auto device = ctx.get_proxy();

        REQUIRE(idlver == device->get_idl_version());

        WHEN("we read the attribute")
        {
            std::string att{"attr_dq_db"};

            Tango::DeviceAttribute da;
            REQUIRE_NOTHROW(da = device->read_attribute(att));

            THEN("the read value matches the value on the server")
            {
                double att_value;
                da >> att_value;
                REQUIRE(att_value == SERVER_VALUE);
            }

            auto contents = load_file(ctx.get_redirect_file());
            REQUIRE(!contents.empty());

            using namespace Catch::Matchers;

            REQUIRE_THAT(contents, ContainsSubstring("code.filepath:"));

            if(idlver > 3)
            {
                REQUIRE_THAT(contents, ContainsSubstring("myKey"));
                REQUIRE_THAT(contents, !ContainsSubstring("severity_text"));

                // Ensure that both trace exporters are outputting
                int n_found = 0;
                std::string::size_type index = 0;
                while((index = contents.find("myValue", index)) != std::string::npos)
                {
                    ++n_found;
                    index += 7;
                }
                REQUIRE(n_found == 2);
            }

            // Ensure short function names are used for span names
            REQUIRE_THAT(contents, ContainsSubstring(" : TelemetryDS<"));
        }
    }
}

SCENARIO("Telemetry logs are outputted")
{
    int idlver = GENERATE(TangoTest::idlversion(3));
    GIVEN("a device proxy to a simple IDLv" << idlver << " device")
    {
        std::vector<std::string> env{"TANGO_TELEMETRY_ENABLE=on",
                                     "TANGO_TELEMETRY_TYPES=logging",
                                     "TANGO_TELEMETRY_LOGGING_EXPORTERS=console",
                                     "TANGO_TELEMETRY_LOGGING_ENDPOINTS=cout"};
        TangoTest::Context ctx{"telemetry", "TelemetryDS", idlver, env};
        auto device = ctx.get_proxy();

        REQUIRE(idlver == device->get_idl_version());

        WHEN("we read the attribute")
        {
            std::string att{"attr_dq_db"};

            Tango::DeviceAttribute da;
            REQUIRE_NOTHROW(da = device->read_attribute(att));

            THEN("the read value matches the value on the server")
            {
                double att_value;
                da >> att_value;
                REQUIRE(att_value == SERVER_VALUE);
            }

            auto contents = load_file(ctx.get_redirect_file());
            REQUIRE(!contents.empty());

            using namespace Catch::Matchers;

            REQUIRE_THAT(contents, ContainsSubstring("code.filepath:"));

            if(idlver > 3)
            {
                REQUIRE_THAT(contents, !ContainsSubstring("myKey"));
                REQUIRE_THAT(contents, ContainsSubstring("severity_text"));
            }
        }
    }
}

SCENARIO("Telemetry does complain about invalid environment variables")
{
    int idlver = GENERATE(TangoTest::idlversion(6));
    GIVEN("a device proxy to a simple IDLv" << idlver << " device")
    {
        struct TestData
        {
            std::string telemetry, types, traces_exporter, logs_exporter, traces_endpoint, logs_endpoint;
        };

        auto data = GENERATE(TestData{"bogus", "tracing", "console", "console", "cout", "cout"},
                             TestData{"on", "bogus", "console", "console", "cout", "cout"},
                             TestData{"on", "tracing", "bogus", "console", "cout", "cout"},
                             TestData{"on", "tracing", "console", "bogus", "cout", "cout"},
                             TestData{"on", "tracing", "console", "console", "bogus", "cout"},
                             TestData{"on", "tracing", "console", "console", "cout", "bogus"},
                             TestData{"on", "tracing,bogus", "console", "console", "cout", "cout"},
                             TestData{"on", "tracing", "console,bogus", "console", "cout", "cout"},
                             TestData{"on", "tracing", "console", "console", "cout,cerr", "cout"},
                             TestData{"on", "tracing", "console", "console", "cout", "cout,cerr"},
                             TestData{"on", "tracing", "console", "console,console", "cout", "cout,bogus"});

        WHEN("set the environment variables")
        {
            std::vector<std::string> env{"TANGO_TELEMETRY_ENABLE=" + data.telemetry,
                                         "TANGO_TELEMETRY_TYPES=" + data.types,
                                         "TANGO_TELEMETRY_TRACING_EXPORTERS=" + data.traces_exporter,
                                         "TANGO_TELEMETRY_LOGGING_EXPORTERS=" + data.logs_exporter,
                                         "TANGO_TELEMETRY_TRACING_ENDPOINTS=" + data.traces_endpoint,
                                         "TANGO_TELEMETRY_LOGGING_ENDPOINTS=" + data.logs_endpoint};

            INFO(Catch::StringMaker<std::vector<std::string>>::convert(env));

            // avoid parsing issues in REQUIRE_XXX macro
            auto f = [&idlver, &env]() { TangoTest::Context ctx{"telemetry", "TelemetryDS", idlver, env}; };

            using namespace Catch::Matchers;

            REQUIRE_THROWS_MATCHES(
                f(), std::runtime_error, MessageMatches(ContainsSubstring("Error reason = API_InvalidArgs")));
        }
    }
}

SCENARIO("Telemetry can be configured for all variants")
{
    int idlver = GENERATE(TangoTest::idlversion(6));
    GIVEN("a device proxy to a simple IDLv" << idlver << " device")
    {
        struct TestData
        {
            std::string traces_exporter, logs_exporter, traces_endpoint, logs_endpoint;
        };

        auto data = GENERATE(
            TestData{"console", "console", "cout", "cerr"},                                                  // console
            TestData{"http", "http", "http://localhost:4711/v1/traces", "https://localhost:4712/v1/traces"}, // http
            TestData{"grpc", "grpc", "grpc://localhost:4711", "grpc://localhost:4712"},                      // grpc
            TestData{"grpc,console", // multiple exporters
                     "grpc,http",
                     "grpc://localhost:4711,cout",
                     "grpc://localhost:4711,http://localhost:4711/v1/trace"},
            TestData{"grpc", "http,console", "", ""}); // default endpoints

        WHEN("set the environment variables")
        {
            std::vector<std::string> env{"TANGO_TELEMETRY_ENABLE=off",
                                         "TANGO_TELEMETRY_TYPES=none",
                                         "TANGO_TELEMETRY_TRACING_EXPORTERS=" + data.traces_exporter,
                                         "TANGO_TELEMETRY_LOGGING_EXPORTERS=" + data.logs_exporter};
            if(data.traces_endpoint != "")
            {
                env.emplace_back("TANGO_TELEMETRY_TRACING_ENDPOINTS=" + data.traces_endpoint);
            }
            if(data.logs_endpoint != "")
            {
                env.emplace_back("TANGO_TELEMETRY_LOGGING_ENDPOINTS=" + data.logs_endpoint);
            }

            auto f = [&idlver, &env]() { TangoTest::Context ctx{"telemetry", "TelemetryDS", idlver, env}; };
            REQUIRE_NOTHROW(f());
        }
    }
}

SCENARIO("Telemetry traces/logs can be turned off")
{
    int idlver = GENERATE(TangoTest::idlversion(6));
    GIVEN("a device proxy to a simple IDLv" << idlver << " device")
    {
        std::vector<std::string> env{"TANGO_TELEMETRY_ENABLE=on", "TANGO_TELEMETRY_TYPES=none"};
        TangoTest::Context ctx{"telemetry", "TelemetryDS", idlver, env};
        auto device = ctx.get_proxy();

        REQUIRE(idlver == device->get_idl_version());

        WHEN("we read the attribute")
        {
            std::string att{"attr_dq_db"};

            Tango::DeviceAttribute da;
            REQUIRE_NOTHROW(da = device->read_attribute(att));

            THEN("the read value matches the value on the server")
            {
                double att_value;
                da >> att_value;
                REQUIRE(att_value == SERVER_VALUE);
            }

            auto contents = load_file(ctx.get_redirect_file());
            REQUIRE(!contents.empty());

            using namespace Catch::Matchers;

            REQUIRE_THAT(contents, !ContainsSubstring("code.filepath:"));
        }
    }
}

SCENARIO("Telemetry admin commands: telemetry on/off")
{
    int idlver = GENERATE(TangoTest::idlversion(6));

    GIVEN("a device proxy to a simple IDLv" << idlver << " device and a connection to the admin device")
    {
        std::vector<std::string> env{"TANGO_TELEMETRY_ENABLE=off", "TANGO_TELEMETRY_TYPES=tracing,logging"};
        TangoTest::Context ctx{"telemetry", "TelemetryDS", idlver, env};

        auto device = ctx.get_proxy();

        auto admin = std::make_unique<Tango::DeviceProxy>(device->adm_name());
        REQUIRE(admin != nullptr);

        const std::string devname = "TestServer/tests/1";

        Tango::DeviceData ddin, ddout;
        std::vector<std::string> instr, outstr;
        std::vector<Tango::DevLong> inlong, outlong;

        using namespace TangoTest::Matchers;
        using namespace Catch::Matchers;

        instr = {devname};
        ddin << instr;
        WHEN("we query telemetry status")
        {
            // Check telemetry off
            ddout = admin->command_inout("IsTelemetryEnabled", ddin);
            ddout.extract(outlong, outstr);
            THEN("we find that telemetry is off")
            {
                CHECK_THAT(outlong, RangeEquals(std::vector<long>{0}));
                CHECK_THAT(outstr, RangeEquals(std::vector<std::string>{devname}));
            }
        }

        WHEN("we turn telemetry on and we query telemetry status")
        {
            // Turn telemetry on and check
            ddout = admin->command_inout("StartTelemetry", ddin);
            ddout.reset_exceptions(Tango::DeviceData::isempty_flag);
            CHECK(ddout.is_empty());

            ddout = admin->command_inout("IsTelemetryEnabled", ddin);
            ddout.extract(outlong, outstr);

            THEN("we find that telemetry is enabled")
            {
                CHECK_THAT(outlong, RangeEquals(std::vector<long>{1}));
                CHECK_THAT(outstr, RangeEquals(std::vector<std::string>{devname}));
            }
        }

        WHEN("we turn telemetry on and then off and query telemetry status")
        {
            // Turn telemetry on and check
            ddout = admin->command_inout("StartTelemetry", ddin);
            ddout.reset_exceptions(Tango::DeviceData::isempty_flag);
            CHECK(ddout.is_empty());

            // Turn telemetry off and check
            ddout = admin->command_inout("StopTelemetry", ddin);
            ddout.reset_exceptions(Tango::DeviceData::isempty_flag);
            CHECK(ddout.is_empty());

            ddout = admin->command_inout("IsTelemetryEnabled", ddin);
            ddout.extract(outlong, outstr);
            THEN("we find that telemetry is off")
            {
                CHECK_THAT(outlong, RangeEquals(std::vector<long>{0}));
                CHECK_THAT(outstr, RangeEquals(std::vector<std::string>{devname}));
            }
        }
    }
}

SCENARIO("Telemetry admin commands: logging/tracing status")
{
    int idlver = GENERATE(TangoTest::idlversion(6));

    GIVEN("a device proxy to a simple IDLv" << idlver << " device and a connection to the admin device")
    {
        std::vector<std::string> env{"TANGO_TELEMETRY_ENABLE=off", "TANGO_TELEMETRY_TYPES=tracing,logging"};
        TangoTest::Context ctx{"telemetry", "TelemetryDS", idlver, env};

        auto device = ctx.get_proxy();

        auto admin = std::make_unique<Tango::DeviceProxy>(device->adm_name());
        REQUIRE(admin != nullptr);

        const std::string devname = "TestServer/tests/1";

        Tango::DeviceData ddin, ddout;
        std::vector<std::string> instr, outstr;
        std::vector<Tango::DevLong> inlong, outlong;

        using namespace TangoTest::Matchers;
        using namespace Catch::Matchers;

        std::string telemetry_type = GENERATE("Tracing", "Logging");
        std::string get_command = "GetTelemetry" + telemetry_type;
        std::string set_command = "SetTelemetry" + telemetry_type;

        instr = {devname};

        WHEN("we query " + telemetry_type + " status before turning off")
        {
            // Check type on
            ddin << instr;
            ddout = admin->command_inout(get_command, ddin);
            ddout.extract(outlong, outstr);

            THEN("we find that " + telemetry_type + " is on")
            {
                CHECK_THAT(outlong, RangeEquals(std::vector<long>{1}));
                CHECK_THAT(outstr, RangeEquals(std::vector<std::string>{devname}));
            }
        }

        WHEN("we turn " + telemetry_type + " off and query status")
        {
            // Turn type off and check
            inlong = {0};
            ddin.insert(inlong, instr);
            ddout = admin->command_inout(set_command, ddin);
            ddout.reset_exceptions(Tango::DeviceData::isempty_flag);
            REQUIRE(ddout.is_empty());

            ddin << instr;
            ddout = admin->command_inout(get_command, ddin);
            ddout.extract(outlong, outstr);
            THEN("we find that " + telemetry_type + " is off")
            {
                CHECK_THAT(outlong, RangeEquals(std::vector<long>{0}));
                CHECK_THAT(outstr, RangeEquals(std::vector<std::string>{devname}));
            }
        }
    }
}

SCENARIO("Telemetry admin commands: logging/tracing endpoints")
{
    int idlver = GENERATE(TangoTest::idlversion(6));

    GIVEN("a device proxy to a simple IDLv" << idlver << " device and a connection to the admin device")
    {
        std::vector<std::string> env{"TANGO_TELEMETRY_ENABLE=off", "TANGO_TELEMETRY_TYPES=tracing,logging"};
        TangoTest::Context ctx{"telemetry", "TelemetryDS", idlver, env};

        auto device = ctx.get_proxy();

        auto admin = std::make_unique<Tango::DeviceProxy>(device->adm_name());
        REQUIRE(admin != nullptr);

        const std::string devname = "TestServer/tests/1";

        Tango::DeviceData ddin, ddout;
        std::vector<std::string> instr, outstr;
        std::vector<Tango::DevLong> inlong, outlong;

        using namespace TangoTest::Matchers;
        using namespace Catch::Matchers;

        std::string telemetry_type = GENERATE("Tracing", "Logging");
        std::string get_command = "GetTelemetry" + telemetry_type + "Endpoints";
        std::string set_command = "SetTelemetry" + telemetry_type + "Endpoints";
        std::string add_command = "AddTelemetry" + telemetry_type + "Endpoints";
        std::string remove_command = "RemoveTelemetry" + telemetry_type + "Endpoints";

        WHEN("we query " + telemetry_type + " endpoints")
        {
            // Check default endpoint set
            instr = {devname};
            ddin << instr;
            ddout = admin->command_inout(get_command, ddin);
            ddout >> outstr;

            THEN("we find the defaults are set")
            {
                CHECK_THAT(outstr, RangeEquals(std::vector<std::string>{devname, "1", "console", "cout"}));
            }
        }

        WHEN("we change the " + telemetry_type + " endpoint to {console, cerr}")
        {
            // Change endpoint to {console, cerr} and check
            instr = {devname, "1", "console", "cerr"};
            ddin << instr;
            ddout = admin->command_inout(set_command, ddin);
            ddout.reset_exceptions(Tango::DeviceData::isempty_flag);
            REQUIRE(ddout.is_empty());

            instr = {devname};
            ddin << instr;
            ddout = admin->command_inout(get_command, ddin);
            ddout >> outstr;

            THEN("we find the endpoint has been updated")
            {
                CHECK_THAT(outstr, RangeEquals(std::vector<std::string>{devname, "1", "console", "cerr"}));
            }
        }

        WHEN("we add the " + telemetry_type + " endpoint {console, cout}")
        {
            // Change endpoint to {console, cerr} and check
            instr = {devname, "1", "console", "cerr"};
            ddin << instr;
            ddout = admin->command_inout(set_command, ddin);
            ddout.reset_exceptions(Tango::DeviceData::isempty_flag);
            REQUIRE(ddout.is_empty());

            // Add endpoint and check for {console, cerr},{console, cout}
            instr = {devname, "console", "cout"};
            ddin << instr;
            ddout = admin->command_inout(add_command, ddin);
            ddout.reset_exceptions(Tango::DeviceData::isempty_flag);
            REQUIRE(ddout.is_empty());

            instr = {devname};
            ddin << instr;
            ddout = admin->command_inout(get_command, ddin);
            ddout >> outstr;

            THEN("we find the endpoint has been added")
            {
                CHECK_THAT(outstr,
                           RangeEquals(std::vector<std::string>{devname, "2", "console", "cerr", "console", "cout"}));
            }
        }

        WHEN("we remove the " + telemetry_type + " endpoint {console, cerr}")
        {
            // Change endpoint to {console, cerr}
            instr = {devname, "1", "console", "cerr"};
            ddin << instr;
            ddout = admin->command_inout(set_command, ddin);
            ddout.reset_exceptions(Tango::DeviceData::isempty_flag);
            REQUIRE(ddout.is_empty());

            // Add endpoint
            instr = {devname, "console", "cout"};
            ddin << instr;
            ddout = admin->command_inout(add_command, ddin);
            ddout.reset_exceptions(Tango::DeviceData::isempty_flag);
            REQUIRE(ddout.is_empty());

            // Remove endpoint and check for {console, cout}
            instr = {devname, "console", "cerr"};
            ddin << instr;
            ddout = admin->command_inout(remove_command, ddin);
            ddout.reset_exceptions(Tango::DeviceData::isempty_flag);
            REQUIRE(ddout.is_empty());

            instr = {devname};
            ddin << instr;
            ddout = admin->command_inout(get_command, ddin);
            ddout >> outstr;

            CHECK_THAT(outstr, RangeEquals(std::vector<std::string>{devname, "1", "console", "cout"}));
        }
    }
}

SCENARIO("Telemetry admin commands: topics")
{
    int idlver = GENERATE(TangoTest::idlversion(6));

    GIVEN("a device proxy to a simple IDLv" << idlver << " device and a connection to the admin device")
    {
        std::vector<std::string> env{"TANGO_TELEMETRY_ENABLE=off", "TANGO_TELEMETRY_TYPES=tracing,logging"};
        TangoTest::Context ctx{"telemetry", "TelemetryDS", idlver, env};

        auto device = ctx.get_proxy();

        auto admin = std::make_unique<Tango::DeviceProxy>(device->adm_name());
        REQUIRE(admin != nullptr);

        const std::string devname = "TestServer/tests/1";

        Tango::DeviceData ddin, ddout;
        std::vector<std::string> instr, outstr;
        std::vector<Tango::DevLong> inlong, outlong;

        using namespace TangoTest::Matchers;
        using namespace Catch::Matchers;

        WHEN("we query topics")
        {
            instr = {devname};
            ddin << instr;
            ddout = admin->command_inout("GetTelemetryTopics", ddin);
            ddout >> outstr;

            REQUIRE(outstr.size() == 3);

            THEN("we get the default")
            {
                CHECK_THAT(outstr, RangeEquals(std::vector<std::string>{devname, "1", "all"}));
            }
        }

        WHEN("we set topics")
        {
            instr = {devname, "2", "polling", "user"};
            ddin << instr;
            ddout = admin->command_inout("SetTelemetryTopics", ddin);
            ddout.reset_exceptions(Tango::DeviceData::isempty_flag);
            REQUIRE(ddout.is_empty());

            instr = {devname};
            ddin << instr;
            ddout = admin->command_inout("GetTelemetryTopics", ddin);
            ddout >> outstr;

            THEN("the topics are updated")
            {
                CHECK_THAT(outstr, UnorderedRangeEquals(std::vector<std::string>{devname, "2", "polling", "user"}));
            }
        }
    }
}

SCENARIO("Telemetry admin commands: exceptions")
{
    int idlver = GENERATE(TangoTest::idlversion(6));

    GIVEN("a device proxy to a simple IDLv" << idlver << " device and a connection to the admin device")
    {
        std::vector<std::string> env{"TANGO_TELEMETRY_ENABLE=off", "TANGO_TELEMETRY_TYPES=tracing,logging"};
        TangoTest::Context ctx{"telemetry", "TelemetryDS", idlver, env};

        auto device = ctx.get_proxy();

        auto admin = std::make_unique<Tango::DeviceProxy>(device->adm_name());
        REQUIRE(admin != nullptr);

        const std::string devname = "TestServer/tests/1";

        Tango::DeviceData ddin, ddout;
        std::vector<std::string> instr, outstr;
        std::vector<Tango::DevLong> inlong, outlong;

        WHEN("invalid devices are queried")
        {
            using namespace Catch::Matchers;
            std::string command = GENERATE("StartTelemetry",
                                           "StopTelemetry",
                                           "IsTelemetryEnabled",
                                           "GetTelemetryTopics",
                                           "GetTelemetryTracing",
                                           "GetTelemetryLogging",
                                           "GetTelemetryTracingEndpoints",
                                           "GetTelemetryLoggingEndpoints");

            instr = {"fakedevicename"};
            ddin << instr;
            CHECK_THROWS_WITH(admin->command_inout(command, ddin), ContainsSubstring("API_DeviceNotFound"));
        }

        WHEN("incorrect input is given")
        {
            std::string telemetry_type = GENERATE("Tracing", "Logging");
            std::string set_command = "SetTelemetry" + telemetry_type + "Endpoints";
            std::string add_command = "AddTelemetry" + telemetry_type + "Endpoints";
            std::string remove_command = "RemoveTelemetry" + telemetry_type + "Endpoints";

            using namespace Catch::Matchers;
            instr = {devname, "one", "console", "cout"};
            ddin << instr;
            CHECK_THROWS_WITH(admin->command_inout(set_command, ddin), ContainsSubstring("API_InvalidArgs"));

            instr = {devname, "1", "console", "cout", "extra"};
            ddin << instr;
            CHECK_THROWS_WITH(admin->command_inout(set_command, ddin), ContainsSubstring("API_InvalidArgs"));

            instr = {devname, "2", "console", "cout"};
            ddin << instr;
            CHECK_THROWS_WITH(admin->command_inout(set_command, ddin), ContainsSubstring("API_InvalidArgs"));

            instr = {devname, "1", "console", "cout"};
            ddin << instr;
            CHECK_THROWS_WITH(admin->command_inout(add_command, ddin), ContainsSubstring("API_InvalidArgs"));

            instr = {devname, "1", "console", "cout"};
            ddin << instr;
            CHECK_THROWS_WITH(admin->command_inout(remove_command, ddin), ContainsSubstring("API_InvalidArgs"));
        }
    }
}

SCENARIO("Telemetry topics are respected")
{
    int idlver = GENERATE(TangoTest::idlversion(6));
    GIVEN("a device proxy to a simple IDLv" << idlver << " device")
    {
        std::vector<std::string> env{"TANGO_TELEMETRY_ENABLE=on", "TANGO_TELEMETRY_TYPES=tracing"};
        TangoTest::Context ctx{"telemetry", "TelemetryDS", idlver, env};

        auto device = ctx.get_proxy();

        WHEN("we read the attribute with default topics")
        {
            std::string att{"attr_dq_db"};

            Tango::DeviceAttribute da;
            REQUIRE_NOTHROW(da = device->read_attribute(att));

            double att_value;
            da >> att_value;
            REQUIRE(att_value == SERVER_VALUE);

            // Force spans to flush
            device->set_telemetry_tracing(false);
            auto contents = load_file(ctx.get_redirect_file());
            REQUIRE(!contents.empty());

            using namespace Catch::Matchers;
            THEN("the telemetry span is exported")
            {
                REQUIRE_THAT(contents, ContainsSubstring("myKey"));
            }
        }

        device->set_telemetry_topics({"user"});
        REQUIRE(device->get_telemetry_topics() == std::vector<std::string>{"user"});

        WHEN("we read the attribute after setting the topic to user")
        {
            std::string att{"attr_dq_db"};

            Tango::DeviceAttribute da;
            REQUIRE_NOTHROW(da = device->read_attribute(att));

            double att_value;
            da >> att_value;
            REQUIRE(att_value == SERVER_VALUE);

            // Force spans to flush
            device->set_telemetry_tracing(false);
            auto contents = load_file(ctx.get_redirect_file());
            REQUIRE(!contents.empty());

            using namespace Catch::Matchers;
            THEN("the telemetry span is exported")
            {
                REQUIRE_THAT(contents, ContainsSubstring("myKey"));
            }
        }

        device->set_telemetry_topics({"polling"});
        REQUIRE(device->get_telemetry_topics() == std::vector<std::string>{"polling"});

        WHEN("we read the attribute after setting not-used topic (misc)")
        {
            std::string att{"attr_dq_db"};

            Tango::DeviceAttribute da;
            REQUIRE_NOTHROW(da = device->read_attribute(att));

            double att_value;
            da >> att_value;
            REQUIRE(att_value == SERVER_VALUE);

            // Force spans to flush
            device->set_telemetry_tracing(false);
            auto contents = load_file(ctx.get_redirect_file());
            REQUIRE(!contents.empty());

            using namespace Catch::Matchers;
            THEN("the telemetry span is not exported")
            {
                REQUIRE_THAT(contents, !ContainsSubstring("myKey"));
            }
        }
    }
}

SCENARIO("Telemetry DeviceProxy commands")
{
    int idlver = GENERATE(TangoTest::idlversion(6));
    GIVEN("a device proxy to a simple IDLv" << idlver << " device")
    {
        std::vector<std::string> env{"TANGO_TELEMETRY_ENABLE=off", "TANGO_TELEMETRY_TYPES=tracing,logging"};
        TangoTest::Context ctx{"telemetry", "TelemetryDS", idlver, env};

        auto device = ctx.get_proxy();
        WHEN("query telemetry status before and after turning telemetry on/off")
        {
            REQUIRE(!device->is_telemetry_enabled());
            device->start_telemetry();
            REQUIRE(device->is_telemetry_enabled());
            device->stop_telemetry();
            REQUIRE(!device->is_telemetry_enabled());

            REQUIRE(device->get_telemetry_tracing());
            device->set_telemetry_tracing(false);
            REQUIRE(!device->get_telemetry_tracing());
            device->set_telemetry_tracing(true);
            REQUIRE(device->get_telemetry_tracing());

            REQUIRE(device->get_telemetry_logging());
            device->set_telemetry_logging(false);
            REQUIRE(!device->get_telemetry_logging());
            device->set_telemetry_logging(true);
            REQUIRE(device->get_telemetry_logging());
        }

        WHEN("update tracing endpoints")
        {
            auto result = device->get_telemetry_tracing_endpoints();
            REQUIRE(result == std::vector<std::string>{"console", "cout"});
            device->set_telemetry_tracing_endpoints({"console", "cerr"});
            result = device->get_telemetry_tracing_endpoints();
            REQUIRE(result == std::vector<std::string>{"console", "cerr"});
            device->add_telemetry_tracing_endpoint({"console", "cout"});
            result = device->get_telemetry_tracing_endpoints();
            REQUIRE(result == std::vector<std::string>{"console", "cerr", "console", "cout"});
            device->remove_telemetry_tracing_endpoint({"console", "cerr"});
            result = device->get_telemetry_tracing_endpoints();
            REQUIRE(result == std::vector<std::string>{"console", "cout"});
        }
        WHEN("update logging endpoints")
        {
            auto result = device->get_telemetry_logging_endpoints();
            REQUIRE(result == std::vector<std::string>{"console", "cout"});
            device->set_telemetry_logging_endpoints({"console", "cerr"});
            result = device->get_telemetry_logging_endpoints();
            REQUIRE(result == std::vector<std::string>{"console", "cerr"});
            device->add_telemetry_logging_endpoint({"console", "cout"});
            result = device->get_telemetry_logging_endpoints();
            REQUIRE(result == std::vector<std::string>{"console", "cerr", "console", "cout"});
            device->remove_telemetry_logging_endpoint({"console", "cerr"});
            result = device->get_telemetry_logging_endpoints();
            REQUIRE(result == std::vector<std::string>{"console", "cout"});
        }
    }
}

SCENARIO("Telemetry properties override environment variables")
{
    int idlver = GENERATE(TangoTest::idlversion(6));
    GIVEN("a device proxy to a simple IDLv" << idlver << " device")
    {
        std::vector<std::string> env{"TANGO_TELEMETRY_ENABLE=off",
                                     "TANGO_TELEMETRY_TYPES=none",
                                     "TANGO_TELEMETRY_TOPICS=user",
                                     "TANGO_TELEMETRY_TRACING_EXPORTERS=console",
                                     "TANGO_TELEMETRY_TRACING_ENDPOINTS=cerr",
                                     "TANGO_TELEMETRY_LOGGING_EXPORTERS=grpc,console",
                                     "TANGO_TELEMETRY_LOGGING_ENDPOINTS=grpc://localhost:554,cout"};
        std::string device_properties = "testserver/tests/1->telemetry_enable: true\n \
                                        testserver/tests/1->telemetry_types: \"logging, tracing\"\n \
                                        testserver/tests/1->telemetry_topics: \"events\"\n \
                                        testserver/tests/1->telemetry_tracing_exporters: \"grpc, console\"\n \
                                        testserver/tests/1->telemetry_tracing_endpoints: \"grpc://localhost:554, cerr\"\n \
                                        testserver/tests/1->telemetry_logging_exporters: \"http\"\n \
                                        testserver/tests/1->telemetry_logging_endpoints: \"http://localhost:3000\"\n";
        TangoTest::Context ctx{"telemetry", "TelemetryDS", idlver, device_properties, env};

        auto device = ctx.get_proxy();

        CHECK(device->is_telemetry_enabled());
        CHECK(device->get_telemetry_tracing());
        CHECK(device->get_telemetry_logging());
        CHECK(device->get_telemetry_topics() == std::vector<std::string>{"events"});
        CHECK(device->get_telemetry_tracing_endpoints() ==
              std::vector<std::string>{"grpc", "grpc://localhost:554", "console", "cerr"});
        CHECK(device->get_telemetry_logging_endpoints() == std::vector<std::string>{"http", "http://localhost:3000"});
    }
}

SCENARIO("Telemetry DS can be restarted without memory leak")
{
    int idlver = GENERATE(TangoTest::idlversion(6));
    GIVEN("a device proxy to a simple IDLv" << idlver << " device")
    {
        std::vector<std::string> env{"TANGO_TELEMETRY_ENABLE=on", "TANGO_TELEMETRY_TYPES=none"};
        TangoTest::Context ctx{"telemetry", "TelemetryDS", idlver, env};

        auto device = ctx.get_proxy();
        REQUIRE(idlver == device->get_idl_version());

        auto admin_dev = ctx.get_admin_proxy();
        REQUIRE_NOTHROW(admin_dev->command_inout("RestartServer"));
    }
}
