/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <gtest/gtest.h>

#include "modifier_test_base.h"
#include "modifiers_test_utils.h"

#include "core/interfaces/native/utility/converter.h"
#include "core/interfaces/native/utility/reverse_converter.h"
#include "core/components_ng/pattern/calendar_picker/calendar_picker_event_hub.h"
#include "core/components_ng/pattern/date_picker/picker_theme.h"
#include "core/components/button/button_theme.h"
#include "core/components/calendar/calendar_theme.h"
#include "core/components/theme/shadow_theme.h"
#include "core/components/theme/icon_theme.h"

using namespace testing;
using namespace testing::ext;

namespace OHOS::Ace::NG {
using namespace Converter;
namespace  {
const auto ATTRIBUTE_TEXT_STYLE_NAME = "textStyle";
const auto ATTRIBUTE_EDGE_ALIGN_TYPE_NAME = "edgeAlign";
const auto ATTRIBUTE_EDGE_ALIGN_ALIGN_TYPE_NAME = "alignType";
const auto ATTRIBUTE_EDGE_ALIGN_OFFSET_NAME = "offset";
const auto ATTRIBUTE_TEXT_STYLE_COLOR_NAME = "color";
const auto ATTRIBUTE_FONT_SIZE_DEFAULT_VALUE = "0.00px";
const auto ATTRIBUTE_TEXT_STYLE_FONT_NAME = "font";
const auto ATTRIBUTE_FONT_SIZE_NAME = "size";
const auto ATTRIBUTE_FONT_WEIGHT_NAME = "weight";
const auto ATTRIBUTE_HINT_RADIUS_NAME = "hintRadius";
const auto ATTRIBUTE_SELECTED_NAME = "selected";
const auto ATTRIBUTE_MARK_TODAY_NAME = "markToday";
const auto ATTRIBUTE_MARK_TODAY_DEFAULT_VALUE = "false";

const auto RES_COLOR_ID = IntResourceId{102001, ResourceType::COLOR};
const auto RES_COLOR_ID_VALUE = Color(0xFF654321);
const auto RES_COLOR_NAME = NamedResourceId{"color_name", ResourceType::COLOR};
const auto RES_COLOR_NAME_VALUE = Color(0xFF123456);

const auto RES_FONT_FAMILY_NAME = NamedResourceId{"res_font_family_id", ResourceType::STRARRAY};
const auto RES_FONT_FAMILY_NAME_VALUE = "res_font_family_string_id";
const auto RES_FONT_FAMILY_ID = IntResourceId{102002, ResourceType::STRARRAY};
const auto RES_FONT_FAMILY_ID_VALUE = "res_font_family_number_id";

struct CalendarAlignTest {
    Ark_CalendarAlign calendarAlignType;
    Ark_Offset offset;
    std::string expectedCalendarAlignType;
    std::string expectedDx;
    std::string expectedDy;
};

const Ark_Float32 AFLT32_POS(1.234f);
const Ark_Float32 AFLT32_NEG(-5.6789f);
const auto CHECK_AFLT32_POS = "1.23vp";

const auto RES_CONTENT_STR = "aa.bb.cc";
const auto RES_CONTENT = Converter::ArkValue<Ark_String>(RES_CONTENT_STR);
const auto RES_NAME = NamedResourceId{"res_name", ResourceType::STRING};
const Opt_Union_String_Resource OPT_UNION_RESOURCE_RESOURCE = CreateResourceUnion<Opt_Union_String_Resource>(RES_NAME);
const std::string CHECK_RESOURCE_STR(RES_CONTENT_STR);

typedef std::pair<Opt_Union_String_Resource, std::string> UnionStringResourceTestStep;
const std::vector<UnionStringResourceTestStep> UNION_RESOURCE_STRING_PLAN = {
    { Converter::ArkUnion<Opt_Union_String_Resource, Ark_String>(RES_CONTENT), CHECK_RESOURCE_STR },
    { OPT_UNION_RESOURCE_RESOURCE, CHECK_RESOURCE_STR }
};

const float TEST_OFFSET_X1 = 11.f;
const float TEST_OFFSET_Y1 = 35.f;
const Ark_Offset offset1 = { .dx = ArkValue<Ark_Length>(TEST_OFFSET_X1), .dy = ArkValue<Ark_Length>(TEST_OFFSET_Y1) };

const float TEST_OFFSET_X2 = 25.f;
const float TEST_OFFSET_Y2 = 40.f;
const Ark_Offset offset2 = { .dx = ArkValue<Ark_Length>(TEST_OFFSET_X2), .dy = ArkValue<Ark_Length>(TEST_OFFSET_Y2) };

const std::vector<CalendarAlignTest> CALENDAR_ALIGN_TEST_PLAN = {
    {
        .calendarAlignType = ARK_CALENDAR_ALIGN_START, .expectedCalendarAlignType = "CalendarAlign.START",
        .offset = offset1, .expectedDx = "11.00vp", .expectedDy = "35.00vp"
    },
    {
        .calendarAlignType = ARK_CALENDAR_ALIGN_CENTER, .expectedCalendarAlignType = "CalendarAlign.CENTER",
        .offset = offset1, .expectedDx = "11.00vp", .expectedDy = "35.00vp"
    },
    {
        .calendarAlignType = ARK_CALENDAR_ALIGN_END, .expectedCalendarAlignType = "CalendarAlign.END",
        .offset = offset1, .expectedDx = "11.00vp", .expectedDy = "35.00vp"
    },
    {
        .calendarAlignType = ARK_CALENDAR_ALIGN_END, .expectedCalendarAlignType = "CalendarAlign.END",
        .offset = offset2, .expectedDx = "25.00vp", .expectedDy = "40.00vp"
    }
};

std::vector<std::tuple<std::string, Opt_Boolean, std::string>> testFixtureBooleanValidValues = {
    { "true", Converter::ArkValue<Opt_Boolean>(true), "true" },
    { "false", Converter::ArkValue<Opt_Boolean>(false), "false" },
    { "true", Converter::ArkValue<Opt_Boolean>(true), "true" },
    { "Ark_Empty", Converter::ArkValue<Opt_Boolean>(Ark_Empty()), "false" },
};
} // namespace

class CalendarPickerModifierTest : public ModifierTestBase<
    GENERATED_ArkUICalendarPickerModifier,
    &GENERATED_ArkUINodeModifiers::getCalendarPickerModifier,
    GENERATED_ARKUI_CALENDAR_PICKER
> {
public:
    static void SetUpTestCase()
    {
        ModifierTestBase::SetUpTestCase();

        SetupTheme<ButtonTheme>();
        SetupTheme<CalendarTheme>();
        SetupTheme<IconTheme>();
        SetupTheme<PickerTheme>();
        SetupTheme<ShadowTheme>();
        SetupTheme<TextTheme>();

        AddResource(RES_FONT_FAMILY_NAME, RES_FONT_FAMILY_NAME_VALUE);
        AddResource(RES_FONT_FAMILY_ID, RES_FONT_FAMILY_ID_VALUE);
        AddResource(RES_COLOR_ID, RES_COLOR_ID_VALUE);
        AddResource(RES_COLOR_NAME, RES_COLOR_NAME_VALUE);
    }
};

/*
 * @tc.name: setEdgeAlignTest
 * @tc.desc:
 * @tc.type: FUNC
 */
HWTEST_F(CalendarPickerModifierTest, setEdgeAlignTest, TestSize.Level1)
{
    ASSERT_NE(modifier_->setEdgeAlign, nullptr);

    for (const auto& data: CALENDAR_ALIGN_TEST_PLAN) {
        auto optOffset = ArkValue<Opt_Offset>(data.offset);
        auto align = Converter::ArkValue<Opt_CalendarAlign>(data.calendarAlignType);
        modifier_->setEdgeAlign(node_, &align, &optOffset);

        auto fullJson = GetJsonValue(node_);

        auto edgeAlignJson = GetAttrObject(fullJson, ATTRIBUTE_EDGE_ALIGN_TYPE_NAME);
        ASSERT_NE(edgeAlignJson, nullptr);

        auto alignTypeStr = GetAttrValue<std::string>(edgeAlignJson, ATTRIBUTE_EDGE_ALIGN_ALIGN_TYPE_NAME);
        EXPECT_THAT(alignTypeStr, Eq(data.expectedCalendarAlignType));

        auto offsetJson = GetAttrObject(edgeAlignJson, ATTRIBUTE_EDGE_ALIGN_OFFSET_NAME);
        ASSERT_NE(offsetJson, nullptr);
        auto actualDx = GetAttrValue<std::string>(offsetJson, "dX");
        auto actualDy = GetAttrValue<std::string>(offsetJson, "dY");
        EXPECT_THAT(actualDx, Eq(data.expectedDx));
        EXPECT_THAT(actualDy, Eq(data.expectedDy));
    };
}

typedef std::pair<Opt_ResourceColor, std::string> ArkColorTest;
const std::vector<ArkColorTest> COLOR_TEST_PLAN = {
    { Converter::ArkUnion<Opt_ResourceColor, Ark_Color>(ARK_COLOR_WHITE), "#FFFFFFFF" },
    { Converter::ArkUnion<Opt_ResourceColor, Ark_Color>(ARK_COLOR_BLACK), "#FF000000" },
    { Converter::ArkUnion<Opt_ResourceColor, Ark_Color>(ARK_COLOR_BLUE), "#FF0000FF" },
    { Converter::ArkUnion<Opt_ResourceColor, Ark_Color>(ARK_COLOR_BROWN), "#FFA52A2A" },
    { Converter::ArkUnion<Opt_ResourceColor, Ark_Color>(ARK_COLOR_GRAY), "#FF808080" },
    { Converter::ArkUnion<Opt_ResourceColor, Ark_Color>(ARK_COLOR_GREEN), "#FF008000" },
    { Converter::ArkUnion<Opt_ResourceColor, Ark_Color>(ARK_COLOR_GREY), "#FF808080" },
    { Converter::ArkUnion<Opt_ResourceColor, Ark_Color>(ARK_COLOR_ORANGE), "#FFFFA500" },
    { Converter::ArkUnion<Opt_ResourceColor, Ark_Color>(ARK_COLOR_PINK), "#FFFFC0CB" },
    { Converter::ArkUnion<Opt_ResourceColor, Ark_Color>(ARK_COLOR_RED), "#FFFF0000" },
    { Converter::ArkUnion<Opt_ResourceColor, Ark_Color>(ARK_COLOR_YELLOW), "#FFFFFF00" },
    { Converter::ArkUnion<Opt_ResourceColor, Ark_Color>(ARK_COLOR_TRANSPARENT), "#00000000" },
};

typedef std::pair<Opt_Union_FontWeight_I32_String, std::string> ArkFontWeightTest;
const std::vector<ArkFontWeightTest> FONT_WEIGHT_TEST_PLAN = {
    { Converter::ArkUnion<Opt_Union_FontWeight_I32_String, Ark_FontWeight>(ARK_FONT_WEIGHT_LIGHTER),
        "FontWeight.Lighter" },
    { Converter::ArkUnion<Opt_Union_FontWeight_I32_String, Ark_FontWeight>(ARK_FONT_WEIGHT_NORMAL),
        "FontWeight.Normal" },
    { Converter::ArkUnion<Opt_Union_FontWeight_I32_String, Ark_FontWeight>(ARK_FONT_WEIGHT_REGULAR),
        "FontWeight.Regular" }
};

typedef std::pair<Opt_Length, std::string> OptLengthTestStep;
const std::vector<OptLengthTestStep> FONT_SIZE_TEST_PLAN = {
    { Converter::ArkValue<Opt_Length>(AFLT32_POS), CHECK_AFLT32_POS },
    { Converter::ArkValue<Opt_Length>(AFLT32_NEG), ATTRIBUTE_FONT_SIZE_DEFAULT_VALUE },
};

/*
 * @tc.name: setTextStyleTestColor
 * @tc.desc:
 * @tc.type: FUNC
 */
HWTEST_F(CalendarPickerModifierTest, setTextStyleTestColor, TestSize.Level1)
{
    ASSERT_NE(modifier_->setTextStyle, nullptr);
    Ark_Font font = {
        .size = FONT_SIZE_TEST_PLAN[0].first,
        .weight = FONT_WEIGHT_TEST_PLAN[0].first
    };
    Ark_PickerTextStyle pickerStyle;
    pickerStyle.font.value = font;

    auto frameNode = reinterpret_cast<FrameNode *>(node_);
    ASSERT_NE(frameNode, nullptr);

    for (auto color : COLOR_TEST_PLAN) {
        pickerStyle.color = color.first;
        auto optPickerStyle = Converter::ArkValue<Opt_PickerTextStyle>(pickerStyle);
        modifier_->setTextStyle(node_, &optPickerStyle);
        auto fullJson = GetJsonValue(node_);
        auto styleObject = GetAttrObject(fullJson, ATTRIBUTE_TEXT_STYLE_NAME);
        auto checkColor = GetAttrValue<std::string>(styleObject, ATTRIBUTE_TEXT_STYLE_COLOR_NAME);
        EXPECT_THAT(checkColor, Eq(color.second));
    }
}

/*
 * @tc.name: setTextStyleTestFontWeight
 * @tc.desc:
 * @tc.type: FUNC
 */
HWTEST_F(CalendarPickerModifierTest, DISABLED_setTextStyleTestFontWeight, TestSize.Level1)
{
    ASSERT_NE(modifier_->setTextStyle, nullptr);
    Ark_Font font = {
        .size = FONT_SIZE_TEST_PLAN[0].first,
        .weight = FONT_WEIGHT_TEST_PLAN[0].first
    };
    Ark_PickerTextStyle pickerStyle;
    auto sizeStr = FONT_SIZE_TEST_PLAN[0].second;

    auto frameNode = reinterpret_cast<FrameNode *>(node_);
    ASSERT_NE(frameNode, nullptr);

    for (auto weight : FONT_WEIGHT_TEST_PLAN) {
        font.weight = weight.first;
        pickerStyle.font.value = font;
        auto optPickerStyle = Converter::ArkValue<Opt_PickerTextStyle>(pickerStyle);
        modifier_->setTextStyle(node_, &optPickerStyle);
        auto fullJson = GetJsonValue(node_);
        auto styleObject = GetAttrObject(fullJson, ATTRIBUTE_TEXT_STYLE_NAME);
        auto fontObject = GetAttrObject(styleObject, ATTRIBUTE_TEXT_STYLE_FONT_NAME);
        auto checkSize = GetAttrValue<std::string>(fontObject, ATTRIBUTE_FONT_SIZE_NAME);
        auto checkWeight = GetAttrValue<std::string>(fontObject, ATTRIBUTE_FONT_WEIGHT_NAME);
        EXPECT_THAT(checkSize, Eq(sizeStr));
        EXPECT_THAT(checkWeight, Eq(weight.second));
    }
}

/*
 * @tc.name: setTextStyleTestFontSize
 * @tc.desc:
 * @tc.type: FUNC
 */
HWTEST_F(CalendarPickerModifierTest, DISABLED_setTextStyleTestFontSize, TestSize.Level1)
{
    ASSERT_NE(modifier_->setTextStyle, nullptr);
    Ark_Font font = {
        .size = FONT_SIZE_TEST_PLAN[0].first,
        .weight = FONT_WEIGHT_TEST_PLAN[0].first
    };
    Ark_PickerTextStyle pickerStyle;
    auto weightStr = FONT_WEIGHT_TEST_PLAN[0].second;

    auto frameNode = reinterpret_cast<FrameNode *>(node_);
    ASSERT_NE(frameNode, nullptr);

    for (auto size : FONT_SIZE_TEST_PLAN) {
        font.size = size.first;
        pickerStyle.font.value = font;
        auto optPickerStyle = Converter::ArkValue<Opt_PickerTextStyle>(pickerStyle);
        modifier_->setTextStyle(node_, &optPickerStyle);
        auto fullJson = GetJsonValue(node_);
        auto styleObject = GetAttrObject(fullJson, ATTRIBUTE_TEXT_STYLE_NAME);
        auto fontObject = GetAttrObject(styleObject, ATTRIBUTE_TEXT_STYLE_FONT_NAME);
        auto checkSize = GetAttrValue<std::string>(fontObject, ATTRIBUTE_FONT_SIZE_NAME);
        auto checkWeight = GetAttrValue<std::string>(fontObject, ATTRIBUTE_FONT_WEIGHT_NAME);
        EXPECT_THAT(checkSize, Eq(size.second));
        EXPECT_THAT(checkWeight, Eq(weightStr));
    }
}

typedef std::pair<Opt_Union_F64_Resource, PickerDate> OptionsArgsStepTest;
typedef std::pair<std::string, std::string> PickerOptionsStringStepTest;
typedef std::pair<OptionsArgsStepTest, PickerOptionsStringStepTest> PickerDateOptionsStepTest;
const std::vector<PickerDateOptionsStepTest> OPTIONS_TEST_PLAN = {
    { { Converter::ArkUnion<Opt_Union_F64_Resource, Ark_Float64>(0.7), PickerDate(2023, 7, 21) },
        { "0.7", "2023-7-21" } }
};

/*
 * @tc.name: setCalendarPickerOptionsTest
 * @tc.desc: Check the functionality of CalendarPickerModifier.SetCalendarPickerOptionsImpl
 * @tc.type: FUNC
 */
HWTEST_F(CalendarPickerModifierTest, DISABLED_setCalendarPickerOptionsTest, TestSize.Level1)
{
    ASSERT_NE(modifier_->setCalendarPickerOptions, nullptr);

    for (const auto& [actual, expected] : OPTIONS_TEST_PLAN) {
        Ark_CalendarOptions arkOptions = {
            .hintRadius = Converter::ArkValue<Opt_Union_F64_Resource>(std::get<0>(actual)),
            .selected = Converter::ArkValue<Opt_Date>(std::get<1>(actual)),
        };
        auto optOptions = Converter::ArkValue<Opt_CalendarOptions>(arkOptions);
        modifier_->setCalendarPickerOptions(node_, &optOptions);
        auto fullJson = GetJsonValue(node_);
        auto checkHintRadius = GetAttrValue<std::string>(fullJson, ATTRIBUTE_HINT_RADIUS_NAME);
        auto checkSelected = GetAttrValue<std::string>(fullJson, ATTRIBUTE_SELECTED_NAME);
        EXPECT_THAT(checkHintRadius, Eq(std::get<0>(expected)));
        EXPECT_THAT(checkSelected, Eq(std::get<1>(expected)));
    }
}

typedef std::pair<PickerDate, PickerDate> PickerDateTest;
const std::vector<PickerDateTest> CHANGE_EVENT_TEST_PLAN = {
    { PickerDate(1970, 1, 1), PickerDate(1970, 1, 1) },
    { PickerDate(2020, 12, 10), PickerDate(2020, 12, 10) },
    { PickerDate(2200, 12, 31), PickerDate(1970, 1, 1) },
    { PickerDate(0, -1, -5), PickerDate(1970, 1, 1) },
    { PickerDate(-1, 24, 64), PickerDate(1970, 1, 1) },
};

/*
 * @tc.name: setOnChangeTest
 * @tc.desc: Check the functionality of CalendarPickerModifier.SetOnChange
 * @tc.type: FUNC
 */
HWTEST_F(CalendarPickerModifierTest, setOnChangeTest, TestSize.Level1)
{
    ASSERT_NE(modifier_->setOnChange, nullptr);
    auto frameNode = reinterpret_cast<FrameNode*>(node_);
    ASSERT_NE(frameNode, nullptr);
    auto eventHub = frameNode->GetEventHub<CalendarPickerEventHub>();
    ASSERT_NE(eventHub, nullptr);

    static std::optional<PickerDate> expected = std::nullopt;
    auto onChange = [](const Ark_Int32 resourceId, const Ark_Date parameter) {
        expected = Converter::OptConvert<PickerDate>(parameter);
    };
    Callback_Date_Void func = {
        .resource = Ark_CallbackResource {
            .resourceId = frameNode->GetId(),
            .hold = nullptr,
            .release = nullptr,
        },
        .call = onChange
    };
    auto optFunk = Converter::ArkValue<Opt_Callback_Date_Void>(func);
    modifier_->setOnChange(node_, &optFunk);

    for (const auto& testValue : CHANGE_EVENT_TEST_PLAN) {
        std::string testStr = testValue.first.ToString(true);
        eventHub->UpdateOnChangeEvent(testStr);

        EXPECT_TRUE(expected.has_value());
        EXPECT_EQ(expected->GetYear(), testValue.second.GetYear());
        EXPECT_EQ(expected->GetMonth(), testValue.second.GetMonth());
        EXPECT_EQ(expected->GetDay(), testValue.second.GetDay());
    };
}

/*
 * @tc.name: setMarkTodayTestDefaultValues
 * @tc.desc:
 * @tc.type: FUNC
 */
HWTEST_F(CalendarPickerModifierTest, setMarkTodayTestDefaultValues, TestSize.Level1)
{
    std::unique_ptr<JsonValue> jsonValue = GetJsonValue(node_);
    std::optional<std::string> resultStr;

    resultStr = GetAttrValue<std::string>(jsonValue, ATTRIBUTE_MARK_TODAY_NAME);
    EXPECT_THAT(resultStr, Eq(ATTRIBUTE_MARK_TODAY_DEFAULT_VALUE)) << "Default value for attribute 'markToday'";
}

/*
 * @tc.name: setMarkTodayTestMarkTodayValidValues
 * @tc.desc:
 * @tc.type: FUNC
 */
HWTEST_F(CalendarPickerModifierTest, setMarkTodayTestMarkTodayValidValues, TestSize.Level1)
{
    Opt_Boolean initValueMarkToday;

    // Initial setup
    initValueMarkToday = std::get<1>(testFixtureBooleanValidValues[0]);

    auto checkValue = [this, &initValueMarkToday](
                          const std::string& input, const std::string& expectedStr, const Opt_Boolean& value) {
        Opt_Boolean inputValueMarkToday = initValueMarkToday;

        inputValueMarkToday = value;
        modifier_->setMarkToday(node_, &inputValueMarkToday);
        auto jsonValue = GetJsonValue(node_);
        auto resultStr = GetAttrValue<std::string>(jsonValue, ATTRIBUTE_MARK_TODAY_NAME);
        EXPECT_THAT(resultStr, Eq(expectedStr)) <<
            "Input value is: " << input << ", method: setMarkToday, attribute: markToday";
    };

    for (auto& [input, value, expected] : testFixtureBooleanValidValues) {
        checkValue(input, expected, value);
    }
}

} // namespace OHOS::Ace::NG