#include <maps/wikimap/mapspro/services/renderer/src/data_sets/include/data_set.h>
#include <maps/wikimap/mapspro/services/renderer/src/data_sets/include/source_layer.h>
#include <library/cpp/testing/gmock_in_unittest/gmock.h>
#include <library/cpp/testing/unittest/registar.h>
#include <library/cpp/testing/common/env.h>
#include <contrib/libs/yaml-cpp/include/yaml-cpp/yaml.h>

namespace maps::wiki::tests {

namespace mr = maps::renderer;

namespace {

using PropertyNames = std::vector<std::string>;
using PropertyExpressions = std::unordered_map<std::string, std::optional<std::string>>;

struct LayerInfo
{
    std::string id;
    uint32_t minzoom;
    std::string zoomExpression;
    PropertyNames propertyNames;
    PropertyExpressions propertyExpressions;
};

} // namespace

Y_UNIT_TEST_SUITE(load_layers_tests)
{

Y_UNIT_TEST(load_layers_test)
{
    const auto layersYaml = R"(
        - id: layer_base
          type: polyline
          minzoom: 7
          zoom_expression: 'zmin <= %Z% + 1 AND %Z% <= zmax'
          table: |
            (
              SELECT
                id,
                the_geom as geom,
                zmin,
                zmax,
                domain_attrs->'rd_el:fc' as fc,
                domain_attrs->'rd_el:struct_type' as struct_type,
                domain_attrs
              FROM objects_l_view
              WHERE domain_attrs?'cat:rd_el'
            )
          properties:
          - name: fc

        - id: layer3
          extend: layer1
          properties:
          - name: struct_type

        - id: layer1
          extend: layer_base
          minzoom: 8
          zoom_expression: 'zmin <= %Z% AND %Z% <= zmax'

        - id: layer2
          extend: layer_base
          properties:
          - name: access_id
            expression: (domain_attrs->'rd_el:access_id')::int
    )";
    auto layersNode = YAML::Load(layersYaml);
    auto layers = maps::wiki::renderer::loadSourceLayers(layersNode);

    auto propertyNames = [](const renderer::SourceLayer& layer) {
        PropertyNames result;
        for (const auto& prop : layer.info.properties()) {
            result.push_back(prop.name);
        }
        return result;
    };

    auto testLayer = [&](const tests::LayerInfo& expected) {
        EXPECT_TRUE(layers.contains(expected.id));
        const auto& layer = layers.at(expected.id);
        EXPECT_EQ(layer.id, expected.id);
        EXPECT_EQ(layer.info.id(), expected.id);
        EXPECT_EQ(layer.minzoom, expected.minzoom);
        EXPECT_EQ(propertyNames(layer), expected.propertyNames);
        EXPECT_EQ(layer.zoomExpression, expected.zoomExpression);
        EXPECT_EQ(layer.propertyExpressions, expected.propertyExpressions);
    };

    testLayer({
        .id = "layer_base",
        .minzoom = 7u,
        .zoomExpression = "zmin <= %Z% + 1 AND %Z% <= zmax",
        .propertyNames = PropertyNames{"fc", "id"},
        .propertyExpressions = (PropertyExpressions{
            {"fc", std::nullopt},
            {"id", std::nullopt}
        })
    });

    testLayer({
        .id = "layer1",
        .minzoom = 8u,
        .zoomExpression = "zmin <= %Z% AND %Z% <= zmax",
        .propertyNames = PropertyNames{"fc", "id"},
        .propertyExpressions = (PropertyExpressions{
            {"fc", std::nullopt},
            {"id", std::nullopt}
        })
    });

    testLayer({
        .id = "layer2",
        .minzoom = 7u,
        .zoomExpression = "zmin <= %Z% + 1 AND %Z% <= zmax",
        .propertyNames = PropertyNames{"access_id", "fc", "id"},
        .propertyExpressions = (PropertyExpressions{
            {"access_id", "(domain_attrs->'rd_el:access_id')::int"},
            {"fc", std::nullopt},
            {"id", std::nullopt}
        })
    });

    testLayer({
        .id = "layer3",
        .minzoom = 8u,
        .zoomExpression = "zmin <= %Z% AND %Z% <= zmax",
        .propertyNames = PropertyNames{"fc", "id", "struct_type"},
        .propertyExpressions = (PropertyExpressions{
            {"fc", std::nullopt},
            {"id", std::nullopt},
            {"struct_type", std::nullopt},
        })
    });
}

Y_UNIT_TEST(non_exists_layer_test)
{
    const auto layersYaml = R"(
        - id: layer
          extend: non_exists_layer
    )";
    auto layersNode = YAML::Load(layersYaml);
    EXPECT_THROW(maps::wiki::renderer::loadSourceLayers(layersNode), maps::Exception);
}

Y_UNIT_TEST(override_property_test)
{
    const auto layersYaml = R"(
        - id: layer_base
          type: polyline
          table: ( SELECT id, the_geom as geom FROM objects_l_view )
          properties:
          - name: access_id
            expression: (domain_attrs->'rd_el:access_id')::int

        - id: layer1
          extend: layer_base
          properties:
          - name: access_id
            expression: (domain_attrs->'rd_el:access_id')::int + 0
    )";
    auto layersNode = YAML::Load(layersYaml);
    EXPECT_THROW(maps::wiki::renderer::loadSourceLayers(layersNode), maps::Exception);
}

Y_UNIT_TEST(circular_inheritance_test)
{
    const auto layersYaml = R"(
        - id: layer1
          extend: layer2

        - id: layer2
          extend: layer3

        - id: layer3
          extend: layer1
    )";
    auto layersNode = YAML::Load(layersYaml);
    EXPECT_THROW(maps::wiki::renderer::loadSourceLayers(layersNode), maps::Exception);
}

Y_UNIT_TEST(feature_generator_test)
{
    // without required property "ft_type_id"
    {
        const auto layersYaml = R"(
            - id: layer_base
              type: polyline
              table: objects_l_view
              feature_generator: road_marking_line
        )";
        auto layersNode = YAML::Load(layersYaml);
        EXPECT_THROW(maps::wiki::renderer::loadSourceLayers(layersNode), maps::Exception);
    }
    // with required property "ft_type_id"
    {
        const auto layersYaml = R"(
            - id: layer_base
              type: polyline
              table: objects_l_view
              feature_generator: road_marking_line
              properties:
                - name: ft_type_id
                  type: number
                  expression: CAST(service_attrs->'srv:ft_type_id' as int) as ft_type_id
        )";
        auto layersNode = YAML::Load(layersYaml);
        EXPECT_NO_THROW(maps::wiki::renderer::loadSourceLayers(layersNode));
    }
}

}; // Y_UNIT_TEST_SUITE renderer_data_sets

} // namespace maps::wiki::tests
