/*
 * Copyright (c) 2024-2026 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 "ecmascript/tests/test_helper.h"
#include "ecmascript/debugger/js_debugger.h"
#include "ecmascript/jspandafile/js_pandafile_manager.h"
#include "assembler/assembly-parser.h"

using namespace panda::ecmascript;
using namespace panda::ecmascript::tooling;
using namespace panda::panda_file;
using namespace panda::pandasm;

namespace panda::ecmascript::tooling {
class JsDebuggerFriendTest {
public:
    explicit JsDebuggerFriendTest(const EcmaVM* vm) : jsDebugger_(vm) {}

    bool HandleStepTest(JSHandle<Method> method, uint32_t bcOffset)
    {
        return jsDebugger_.HandleStep(method, bcOffset);
    }

    bool HandleNativeOutTest()
    {
        return jsDebugger_.HandleNativeOut();
    }

    bool HandleBreakpointTest(JSHandle<Method> method, uint32_t bcOffset)
    {
        return jsDebugger_.HandleBreakpoint(method, bcOffset);
    }

    bool IsBreakpointCondSatisfiedTest(std::optional<JSBreakpoint> breakpoint) const
    {
        return jsDebugger_.IsBreakpointCondSatisfied(breakpoint);
    }

    void SetSymbolicBreakpoint(const std::unordered_set<std::string> &functionNamesSet)
    {
        jsDebugger_.SetSymbolicBreakpoint(functionNamesSet);
    }

    void SetBreakpoint(JSBreakpoint breakpoint)
    {
        jsDebugger_.breakpoints_.insert(breakpoint);
    }

    void SetSmartBreakpoint(JSBreakpoint breakpoint)
    {
        jsDebugger_.smartBreakpoints_.insert(breakpoint);
    }

    void RemoveAllBreakpoints()
    {
        jsDebugger_.RemoveAllBreakpoints();
    }

    bool HasBreakpoints() const
    {
        return !jsDebugger_.breakpoints_.empty() ||
               !jsDebugger_.smartBreakpoints_.empty() ||
               !jsDebugger_.symbolicBreakpoints_.empty();
    }

private:
    JSDebugger jsDebugger_;
};
}

namespace panda::test {
class JsDebuggerTest : public testing::Test {
public:
    static void SetUpTestCase()
    {
        GTEST_LOG_(INFO) << "SetUpTestCase";
    }

    static void TearDownTestCase()
    {
        GTEST_LOG_(INFO) << "TearDownCase";
    }

    void SetUp() override
    {
        TestHelper::CreateEcmaVMWithScope(ecmaVm, thread, scope);
    }

    void TearDown() override
    {
        TestHelper::DestroyEcmaVMWithScope(ecmaVm, scope);
    }

    EcmaVM *ecmaVm {nullptr};
    EcmaHandleScope *scope {nullptr};
    JSThread *thread {nullptr};
};

HWTEST_F_L0(JsDebuggerTest, SetSmartBreakpointTest)
{
    JSDebugger debugger(ecmaVm);
    Parser parser;
    const char *filename = "__PandaFileTranslatorTest2.pa";
    const char *data = R"(
        .function any func_main_0(any a0, any a1, any a2) {
            ldai 1
            return
        }
    )";
    auto res = parser.Parse(data);
    JSPandaFileManager *pfManager = JSPandaFileManager::GetInstance();
    std::unique_ptr<const File> pfPtr = pandasm::AsmEmitter::Emit(res.Value());
    std::shared_ptr<JSPandaFile> pf = pfManager->NewJSPandaFile(pfPtr.release(), CString(filename));
    const panda_file::File *testpf = pf.get()->GetPandaFile();
    CString descriptor = "example_descriptor";
    std::shared_ptr<JSPandaFile> jspandaFilePtr = std::make_shared<JSPandaFile>(testpf, descriptor);
    pfManager->AddJSPandaFile(jspandaFilePtr);
    JSPandaFile* jspandaFilePtrTest = new JSPandaFile(testpf, descriptor);
    panda_file::File::EntityId entityId(42);

    JSPtLocation location(jspandaFilePtrTest, entityId, 60, "sourceFile.js");
    bool result = debugger.RemoveBreakpoint(location);
    EXPECT_EQ(result, false);
    result = debugger.SetSmartBreakpoint(location);
    EXPECT_EQ(result, false);
}

HWTEST_F_L0(JsDebuggerTest, JsDebuggerHooksNullTest)
{
    JSHandle<Method> methodHandle;
    uint32_t bcOffsetTest = 42;
    JSHandle<JSTaggedValue> envHandle;
    JSDebugger debugger(ecmaVm);
    debugger.MethodEntry(methodHandle, envHandle);

    bool result = debugger.HandleDebuggerStmt(methodHandle, bcOffsetTest);
    EXPECT_EQ(result, false);

    JsDebuggerFriendTest debuggerFriend(ecmaVm);
    result = debuggerFriend.HandleNativeOutTest();
    EXPECT_EQ(result, false);

    result = debuggerFriend.HandleBreakpointTest(methodHandle, bcOffsetTest);
    EXPECT_EQ(result, false);

    result = debuggerFriend.HandleStepTest(methodHandle, bcOffsetTest);
    EXPECT_EQ(result, false);
}

HWTEST_F_L0(JsDebuggerTest, JsDebuggerBreakpointNullTest)
{
    JsDebuggerFriendTest debuggerFriend(ecmaVm);
    std::optional<ecmascript::tooling::JSBreakpoint> breakpoint;
    bool result = debuggerFriend.IsBreakpointCondSatisfiedTest(breakpoint);
    EXPECT_EQ(result, false);
}

HWTEST_F_L0(JsDebuggerTest, RemoveAllBreakpointsTest)
{
    JsDebuggerFriendTest debuggerFriend(ecmaVm);
    Parser parser;
    const char *filename = "__PandaFileTranslatorTest3.pa";
    const char *data = R"(
        .function any func_main_0(any a0, any a1, any a2) {
            ldai 1
            return
        }
    )";
    auto res = parser.Parse(data);
    JSPandaFileManager *pfManager = JSPandaFileManager::GetInstance();
    std::unique_ptr<const File> pfPtr = pandasm::AsmEmitter::Emit(res.Value());
    std::shared_ptr<JSPandaFile> pf = pfManager->NewJSPandaFile(pfPtr.release(), CString(filename));
    const panda_file::File *testpf = pf.get()->GetPandaFile();
    CString descriptor = "example_descriptor2";
    std::shared_ptr<JSPandaFile> jspandaFilePtr = std::make_shared<JSPandaFile>(testpf, descriptor);
    pfManager->AddJSPandaFile(jspandaFilePtr);
    JSPandaFile* jspandaFilePtrTest = new JSPandaFile(testpf, descriptor);
    panda_file::File::EntityId entityId(42);

    std::string sourceFile;
    auto ptMethod = std::make_shared<PtMethod>(jspandaFilePtrTest, entityId, false);
    uint32_t bcOffset = 0;
    JSBreakpoint breakpoint(sourceFile, ptMethod, bcOffset,
                            Global<FunctionRef>(ecmaVm, FunctionRef::Undefined(ecmaVm)));

    debuggerFriend.SetBreakpoint(breakpoint);
    EXPECT_EQ(debuggerFriend.HasBreakpoints(), true);
    debuggerFriend.RemoveAllBreakpoints();
    EXPECT_EQ(debuggerFriend.HasBreakpoints(), false);

    debuggerFriend.SetSmartBreakpoint(breakpoint);
    EXPECT_EQ(debuggerFriend.HasBreakpoints(), true);
    debuggerFriend.RemoveAllBreakpoints();
    EXPECT_EQ(debuggerFriend.HasBreakpoints(), false);

    std::unordered_set<std::string> functionNamesSet;
    functionNamesSet.insert("foo");
    debuggerFriend.SetSymbolicBreakpoint(functionNamesSet);
    EXPECT_EQ(debuggerFriend.HasBreakpoints(), true);
    debuggerFriend.RemoveAllBreakpoints();
    EXPECT_EQ(debuggerFriend.HasBreakpoints(), false);
}
}  // namespace panda::test