#include "library/cpp/json/converter/converter.h"
#include <library/cpp/json/json_reader.h>
#include <library/cpp/json/json_writer.h>
#include "library/cpp/json/writer/json_value.h"
#include <library/cpp/testing/unittest/registar.h>

#include <util/generic/array_ref.h>
#include <util/generic/deque.h>
#include <util/generic/hash.h>
#include <util/generic/list.h>
#include <util/generic/map.h>
#include <util/generic/maybe.h>


namespace NJson {
    void AssertJsonsEqual(const TJsonValue& actualJson, const TJsonValue& expectedJson) {
        const auto actualString = WriteJson(actualJson, /*formatOutput*/ true, /*sortkeys*/ true, /*validateUtf8*/ true);
        const auto expectedString = WriteJson(expectedJson, /*formatOutput*/ true, /*sortkeys*/ true, /*validateUtf8*/ true);
        UNIT_ASSERT_NO_DIFF(actualString, expectedString);
    }

    void AssertJsonsEqual(const TJsonValue& actualJson, TStringBuf expectedString) {
        const auto expectedJson = ReadJsonFastTree(expectedString);
        AssertJsonsEqual(actualJson, expectedJson);
    }

    template<typename T>
    struct TConverterTester {
        using TValues = THashMap<TStringBuf, T>;

        static void TestEncoding(const TValues& values) {
            for (const auto& [serializedValue, value] : values) {
                const auto encodedValue = TConverter<T>::Encode(value);
                AssertJsonsEqual(encodedValue, serializedValue);
            }
        }

        static void TestDecoding(const TValues& values) {
            for (const auto& [serializedValue, value] : values) {
                const auto decodedValue = TConverter<T>::Decode(ReadJsonFastTree(serializedValue));
                UNIT_ASSERT_EQUAL(decodedValue, value);
            }
        }

        static void TestDecodingException(TStringBuf serializedValue) {
            try {
                TConverter<T>::Decode(ReadJsonFastTree(serializedValue));
                UNIT_ASSERT(false);
            } catch (...) {
            }
        }

        static void Test(const TValues& values) {
            TestEncoding(values);
            TestDecoding(values);

            for (const auto& [serializedValue, value] : values) {
                const auto encodedValue = TConverter<T>::Encode(value);
                const auto decodedValue = TConverter<T>::Decode(encodedValue);
                UNIT_ASSERT_EQUAL(value, decodedValue);
            }
        }
    };

    template<typename T>
    requires std::is_floating_point_v<T>
    struct TConverterTester<T> {
        using TValues = THashMap<TStringBuf, T>;

        static void TestDecoding(const TValues& values) {
            for (const auto& [serializedValue, value] : values) {
                {
                    const auto decodedValue = TConverter<T>::Decode(ReadJsonFastTree(serializedValue));
                    UNIT_ASSERT_DOUBLES_EQUAL(decodedValue, value, 0.000001);
                }
            }
        }

        static void Test(const TValues& values) {
            TestDecoding(values);

            for (const auto& [serializedValue, value] : values) {
                {
                    const auto encodedValue = TConverter<T>::Encode(value);
                    const auto decodedValue = TConverter<T>::Decode(encodedValue);
                    UNIT_ASSERT_DOUBLES_EQUAL(decodedValue, value, 0.000001);
                }
            }
        }
    };

    Y_UNIT_TEST_SUITE(ConversionTests) {
        Y_UNIT_TEST(PrimitivesTest) {
            TConverterTester<bool>::Test({{"true", true}, {"false", false}});

            TConverterTester<ui8>::Test({{"0", 0}, {"255", 255}});
            TConverterTester<i8>::Test({{"-128", -128}, {"127", 127}});
            TConverterTester<ui16>::Test({{"0", 0}, {"65535", 65535}});
            TConverterTester<i16>::Test({{"-32768", -32768}, {"32767", 32767}});
            TConverterTester<ui32>::Test({{"0", 0}, {"4294967295", 4294967295}});
            TConverterTester<i32>::Test({{"-2147483648", -2147483648}, {"2147483647", 2147483647}});
            TConverterTester<ui64>::Test({{"0", 0}, {"18446744073709551615", 18446744073709551615u}});
            TConverterTester<i64>::Test({
                {"-9223372036854775808", -9223372036854775808u},
                {"9223372036854775807", 9223372036854775807},
            });

            TConverterTester<i8>::TestDecodingException("128");
            TConverterTester<i8>::TestDecodingException("-129");
            TConverterTester<ui8>::TestDecodingException("256");
            TConverterTester<i16>::TestDecodingException("32768");
            TConverterTester<i16>::TestDecodingException("-32769");
            TConverterTester<ui16>::TestDecodingException("65536");
            TConverterTester<i32>::TestDecodingException("-2147483649");
            TConverterTester<i32>::TestDecodingException("2147483649");
            TConverterTester<ui32>::TestDecodingException("4294967296");

            TConverterTester<unsigned char>::Test({{"0", 0}, {"255", 255}});
            TConverterTester<signed char>::Test({{"-128", -128}, {"127", 127}});
            TConverterTester<unsigned short int>::Test({{"0", 0}, {"65535", 65535}});
            TConverterTester<signed short int>::Test({{"-32768", -32768}, {"32767", 32767}});
            TConverterTester<unsigned int>::Test({{"0", 0}, {"65535", 65535}});
            TConverterTester<signed int>::Test({{"-32768", -32768}, {"32767", 32767}});
            TConverterTester<unsigned long int>::Test({{"0", 0}, {"4294967295", 4294967295}});
            TConverterTester<signed long int>::Test({{"-2147483648", -2147483648}, {"2147483647", 2147483647}});
            TConverterTester<unsigned long long int>::Test({{"0", 0}, {"18446744073709551615", 18446744073709551615u}});
            TConverterTester<signed long long int>::Test({
                {"-9223372036854775808", -9223372036854775808u},
                {"9223372036854775807", 9223372036854775807},
            });

            TConverterTester<size_t>::Test({{"0", 0}, {"65535", 65535}});

            TConverterTester<float>::Test({{"-1.12312", -1.12312}, {"3434.25674", 3434.25674}});
            TConverterTester<double>::Test({{"-1.12312E+42", -1.12312E+42}, {"3.25E+120", 3.25E+120}});
        }

        Y_UNIT_TEST(StringsTest) {
            TConverterTester<TStringBuf>::TestEncoding({
                {R"("Let's permit using of Rust in Arcadia")", "Let's permit using of Rust in Arcadia"},
            });
            TConverterTester<TString>::Test({
                {
                    R"("Всякое непрерывное отображение замкнутого n-мерного шара в себя обладает неподвижной точкой")",
                    "Всякое непрерывное отображение замкнутого n-мерного шара в себя обладает неподвижной точкой",
                },
            });
        }

        Y_UNIT_TEST(MaybeTest) {
            TConverterTester<TMaybe<bool>>::Test({
                {"true", TMaybe<bool>(true)},
                {"null", Nothing()},
                {"false", TMaybe<bool>(false)},
            });
        }

        Y_UNIT_TEST(ArraysTest) {
            TConverterTester<TVector<bool>>::Test({{"[true, true, false]", {true, true, false}}});
            TConverterTester<TList<TString>>::Test({{R"(["a", "b"])", {"a", "b"}}});
            TConverterTester<TDeque<bool>>::Test({{"[false, true, false]", {false, true, false}}});
        }

        Y_UNIT_TEST(MapsTest) {
            TConverterTester<THashMap<TStringBuf, bool>>::TestEncoding({
                {R"({"a": true, "b": false})", {{"a", true}, {"b", false}}},
            });
            TConverterTester<THashMap<TString, bool>>::Test({
                {R"({"a": true, "b": false})", {{"a", true}, {"b", false}}},
            });
            TConverterTester<TMap<TStringBuf, TStringBuf>>::TestEncoding({
                {R"({"a": "A", "b": "B"})", {{"a", "A"}, {"b", "B"}}},
            });
            TConverterTester<TMap<TString, TString>>::Test({
                {R"({"a": "A", "b": "B"})", {{"a", "A"}, {"b", "B"}}},
            });
        }

        Y_UNIT_TEST(NestedTest) {
            TConverterTester<TVector<THashMap<TString, TVector<ui64>>>>::Test({
                {
                    R"([
                        {
                            "Three": [0, 1, 2],
                            "Five": [0, 1, 2, 3, 4]
                        },
                        {
                            "Four": [0, 1, 2, 3],
                            "Six": [0, 1, 2, 3, 4, 5]
                        },
                    ])",
                    {
                        {
                            {"Three", {0, 1, 2}},
                            {"Five", {0, 1, 2, 3, 4}},
                        },
                        {
                            {"Four", {0, 1, 2, 3}},
                            {"Six", {0, 1, 2, 3, 4, 5}},
                        },
                    },
                },
            });
        }
    }
}