// Copyright (C) 2025 Povilas Kanapickas
// SPDX-License-Identifier: GPL-3.0-or-later
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE

#include "context/DocumentContextReader.hpp"
#include "TestUtils.hpp"

#include <gtest/gtest.h>
#include <QSharedPointer>
#include <QTextDocument>

namespace QodeAssist::PluginLLMCore {

void PrintTo(const ContextData &data, std::ostream *os)
{
    *os << "ContextData{prefix="
        << (data.prefix ? data.prefix->toStdString() : "<nullopt>")
        << ", suffix=" << (data.suffix ? data.suffix->toStdString() : "<nullopt>")
        << ", fileContext=" << (data.fileContext ? data.fileContext->toStdString() : "<nullopt>")
        << "}";
}

} // namespace QodeAssist::PluginLLMCore

using namespace QodeAssist::Context;
using namespace QodeAssist::PluginLLMCore;
using namespace QodeAssist::Settings;

class DocumentContextReaderTest : public QObject, public testing::Test
{
    Q_OBJECT

protected:
    QTextDocument *createTestDocument(const QString &text)
    {
        auto *doc = new QTextDocument(this);
        doc->setPlainText(text);
        return doc;
    }

    DocumentContextReader createTestReader(const QString &text)
    {
        return DocumentContextReader(createTestDocument(text), "text/python", "/path/to/file");
    }

    QSharedPointer<CodeCompletionSettings> createSettingsForWholeFile()
    {
        // CodeCompletionSettings is noncopyable
        auto settings = QSharedPointer<CodeCompletionSettings>::create();
        settings->readFullFile.setValue(true);
        return settings;
    }

    QSharedPointer<CodeCompletionSettings> createSettingsForLines(int linesBefore, int linesAfter)
    {
        // CodeCompletionSettings is noncopyable
        auto settings = QSharedPointer<CodeCompletionSettings>::create();
        settings->readFullFile.setValue(false);
        settings->readStringsBeforeCursor.setValue(linesBefore);
        settings->readStringsAfterCursor.setValue(linesAfter);
        return settings;
    }
};

TEST_F(DocumentContextReaderTest, testGetLineText)
{
    auto reader = createTestReader("Line 0\nLine 1\nLine 2");

    EXPECT_EQ(reader.getLineText(0), "Line 0");
    EXPECT_EQ(reader.getLineText(1), "Line 1");
    EXPECT_EQ(reader.getLineText(2), "Line 2");
    EXPECT_EQ(reader.getLineText(0, 4), "Line");
}

TEST_F(DocumentContextReaderTest, testGetContext)
{
    auto reader = createTestReader("Line 0\nLine 1\nLine 2\nLine 3\nLine 4");

    // Unknown cursor position
    EXPECT_EQ(reader.getContextBefore(0, -1, 2), "Line 0");
    EXPECT_EQ(reader.getContextAfter(0, -1, 2), "Line 0\nLine 1");

    EXPECT_EQ(reader.getContextBefore(1, -1, 2), "Line 0\nLine 1");
    EXPECT_EQ(reader.getContextAfter(1, -1, 2), "Line 1\nLine 2");

    EXPECT_EQ(reader.getContextBefore(2, -1, 2), "Line 1\nLine 2");
    EXPECT_EQ(reader.getContextAfter(2, -1, 2), "Line 2\nLine 3");

    EXPECT_EQ(reader.getContextBefore(3, -1, 2), "Line 2\nLine 3");
    EXPECT_EQ(reader.getContextAfter(3, -1, 2), "Line 3\nLine 4");

    // Known cursor position
    EXPECT_EQ(reader.getContextBefore(0, 1, 2), "L");
    EXPECT_EQ(reader.getContextAfter(0, 1, 2), "ine 0\nLine 1");

    EXPECT_EQ(reader.getContextBefore(1, 1, 2), "Line 0\nL");
    EXPECT_EQ(reader.getContextAfter(1, 1, 2), "ine 1\nLine 2");

    EXPECT_EQ(reader.getContextBefore(2, 1, 2), "Line 1\nL");
    EXPECT_EQ(reader.getContextAfter(2, 1, 2), "ine 2\nLine 3");

    EXPECT_EQ(reader.getContextBefore(3, 1, 2), "Line 2\nL");
    EXPECT_EQ(reader.getContextAfter(3, 1, 2), "ine 3\nLine 4");
}

TEST_F(DocumentContextReaderTest, testGetContextWithCopyright)
{
    auto reader = createTestReader("/* Copyright (C) 2024 */\nLine 0\nLine 1\nLine 2\nLine 3");

    // Unknown cursor position
    EXPECT_EQ(reader.getContextBefore(0, -1, 2), "");
    EXPECT_EQ(reader.getContextAfter(0, -1, 2), "Line 0");

    EXPECT_EQ(reader.getContextBefore(1, -1, 2), "Line 0");
    EXPECT_EQ(reader.getContextAfter(1, -1, 2), "Line 0\nLine 1");

    EXPECT_EQ(reader.getContextBefore(2, -1, 2), "Line 0\nLine 1");
    EXPECT_EQ(reader.getContextAfter(2, -1, 2), "Line 1\nLine 2");

    EXPECT_EQ(reader.getContextBefore(3, -1, 2), "Line 1\nLine 2");
    EXPECT_EQ(reader.getContextAfter(3, -1, 2), "Line 2\nLine 3");

    // Known cursor position
    EXPECT_EQ(reader.getContextBefore(0, 1, 2), "");
    EXPECT_EQ(reader.getContextAfter(0, 1, 2), "Line 0");

    EXPECT_EQ(reader.getContextBefore(1, 1, 2), "L");
    EXPECT_EQ(reader.getContextAfter(1, 1, 2), "ine 0\nLine 1");

    EXPECT_EQ(reader.getContextBefore(2, 1, 2), "Line 0\nL");
    EXPECT_EQ(reader.getContextAfter(2, 1, 2), "ine 1\nLine 2");

    EXPECT_EQ(reader.getContextBefore(3, 1, 2), "Line 1\nL");
    EXPECT_EQ(reader.getContextAfter(3, 1, 2), "ine 2\nLine 3");
}

TEST_F(DocumentContextReaderTest, testReadWholeFile)
{
    auto reader = createTestReader("Line 0\nLine 1\nLine 2\nLine 3\nLine 4");

    // Unknown cursor position
    EXPECT_EQ(reader.readWholeFileBefore(0, -1), "Line 0");
    EXPECT_EQ(reader.readWholeFileAfter(0, -1), "Line 0\nLine 1\nLine 2\nLine 3\nLine 4");

    EXPECT_EQ(reader.readWholeFileBefore(1, -1), "Line 0\nLine 1");
    EXPECT_EQ(reader.readWholeFileAfter(1, -1), "Line 1\nLine 2\nLine 3\nLine 4");

    EXPECT_EQ(reader.readWholeFileBefore(2, -1), "Line 0\nLine 1\nLine 2");
    EXPECT_EQ(reader.readWholeFileAfter(2, -1), "Line 2\nLine 3\nLine 4");

    EXPECT_EQ(reader.readWholeFileBefore(3, -1), "Line 0\nLine 1\nLine 2\nLine 3");
    EXPECT_EQ(reader.readWholeFileAfter(3, -1), "Line 3\nLine 4");

    EXPECT_EQ(reader.readWholeFileBefore(4, -1), "Line 0\nLine 1\nLine 2\nLine 3\nLine 4");
    EXPECT_EQ(reader.readWholeFileAfter(4, -1), "Line 4");

    // Known cursor position
    EXPECT_EQ(reader.readWholeFileBefore(0, 1), "L");
    EXPECT_EQ(reader.readWholeFileAfter(0, 1), "ine 0\nLine 1\nLine 2\nLine 3\nLine 4");

    EXPECT_EQ(reader.readWholeFileBefore(1, 1), "Line 0\nL");
    EXPECT_EQ(reader.readWholeFileAfter(1, 1), "ine 1\nLine 2\nLine 3\nLine 4");

    EXPECT_EQ(reader.readWholeFileBefore(2, 1), "Line 0\nLine 1\nL");
    EXPECT_EQ(reader.readWholeFileAfter(2, 1), "ine 2\nLine 3\nLine 4");

    EXPECT_EQ(reader.readWholeFileBefore(3, 1), "Line 0\nLine 1\nLine 2\nL");
    EXPECT_EQ(reader.readWholeFileAfter(3, 1), "ine 3\nLine 4");

    EXPECT_EQ(reader.readWholeFileBefore(4, 1), "Line 0\nLine 1\nLine 2\nLine 3\nL");
    EXPECT_EQ(reader.readWholeFileAfter(4, 1), "ine 4");
}

TEST_F(DocumentContextReaderTest, testReadWholeFileWithCopyright)
{
    auto reader = createTestReader("/* Copyright (C) 2024 */\nLine 0\nLine 1\nLine 2\nLine 3");
    // Unknown cursor position
    EXPECT_EQ(reader.readWholeFileBefore(0, -1), "");
    EXPECT_EQ(reader.readWholeFileAfter(0, -1), "Line 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(1, -1), "Line 0");
    EXPECT_EQ(reader.readWholeFileAfter(1, -1), "Line 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(2, -1), "Line 0\nLine 1");
    EXPECT_EQ(reader.readWholeFileAfter(2, -1), "Line 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(3, -1), "Line 0\nLine 1\nLine 2");
    EXPECT_EQ(reader.readWholeFileAfter(3, -1), "Line 2\nLine 3");

    // Known cursor position
    EXPECT_EQ(reader.readWholeFileBefore(0, 0), "");
    EXPECT_EQ(reader.readWholeFileAfter(0, 0), "Line 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(1, 0), "");
    EXPECT_EQ(reader.readWholeFileAfter(1, 0), "Line 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(2, 0), "Line 0\n");
    EXPECT_EQ(reader.readWholeFileAfter(2, 0), "Line 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(3, 0), "Line 0\nLine 1\n");
    EXPECT_EQ(reader.readWholeFileAfter(3, 0), "Line 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(0, 1), "");
    EXPECT_EQ(reader.readWholeFileAfter(0, 1), "Line 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(1, 1), "L");
    EXPECT_EQ(reader.readWholeFileAfter(1, 1), "ine 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(2, 1), "Line 0\nL");
    EXPECT_EQ(reader.readWholeFileAfter(2, 1), "ine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(3, 1), "Line 0\nLine 1\nL");
    EXPECT_EQ(reader.readWholeFileAfter(3, 1), "ine 2\nLine 3");
}

TEST_F(DocumentContextReaderTest, testReadWholeFileWithMultilineCopyright)
{
    auto reader = createTestReader(
        "/*\n * Copyright (C) 2024\n * \n * This file is part of QodeAssist.\n */\n"
        "Line 0\nLine 1\nLine 2\nLine 3");

    // Unknown cursor position
    EXPECT_EQ(reader.readWholeFileBefore(0, -1), "");
    EXPECT_EQ(reader.readWholeFileAfter(0, -1), "Line 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(1, -1), "");
    EXPECT_EQ(reader.readWholeFileAfter(1, -1), "Line 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(2, -1), "");
    EXPECT_EQ(reader.readWholeFileAfter(2, -1), "Line 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(3, -1), "");
    EXPECT_EQ(reader.readWholeFileAfter(3, -1), "Line 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(4, -1), "");
    EXPECT_EQ(reader.readWholeFileAfter(4, -1), "Line 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(5, -1), "Line 0");
    EXPECT_EQ(reader.readWholeFileAfter(5, -1), "Line 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(6, -1), "Line 0\nLine 1");
    EXPECT_EQ(reader.readWholeFileAfter(6, -1), "Line 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(7, -1), "Line 0\nLine 1\nLine 2");
    EXPECT_EQ(reader.readWholeFileAfter(7, -1), "Line 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(8, -1), "Line 0\nLine 1\nLine 2\nLine 3");
    EXPECT_EQ(reader.readWholeFileAfter(8, -1), "Line 3");

    // Known cursor position
    EXPECT_EQ(reader.readWholeFileBefore(0, 0), "");
    EXPECT_EQ(reader.readWholeFileAfter(0, 0), "Line 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(1, 0), "");
    EXPECT_EQ(reader.readWholeFileAfter(1, 0), "Line 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(2, 0), "");
    EXPECT_EQ(reader.readWholeFileAfter(2, 0), "Line 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(3, 0), "");
    EXPECT_EQ(reader.readWholeFileAfter(3, 0), "Line 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(4, 0), "");
    EXPECT_EQ(reader.readWholeFileAfter(4, 0), "Line 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(5, 0), "");
    EXPECT_EQ(reader.readWholeFileAfter(5, 0), "Line 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(6, 0), "Line 0\n");
    EXPECT_EQ(reader.readWholeFileAfter(6, 0), "Line 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(7, 0), "Line 0\nLine 1\n");
    EXPECT_EQ(reader.readWholeFileAfter(7, 0), "Line 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(8, 0), "Line 0\nLine 1\nLine 2\n");
    EXPECT_EQ(reader.readWholeFileAfter(8, 0), "Line 3");

    EXPECT_EQ(reader.readWholeFileBefore(0, 1), "");
    EXPECT_EQ(reader.readWholeFileAfter(0, 1), "Line 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(1, 1), "");
    EXPECT_EQ(reader.readWholeFileAfter(1, 1), "Line 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(2, 1), "");
    EXPECT_EQ(reader.readWholeFileAfter(2, 1), "Line 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(3, 1), "");
    EXPECT_EQ(reader.readWholeFileAfter(3, 1), "Line 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(4, 1), "");
    EXPECT_EQ(reader.readWholeFileAfter(4, 1), "Line 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(5, 1), "L");
    EXPECT_EQ(reader.readWholeFileAfter(5, 1), "ine 0\nLine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(6, 1), "Line 0\nL");
    EXPECT_EQ(reader.readWholeFileAfter(6, 1), "ine 1\nLine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(7, 1), "Line 0\nLine 1\nL");
    EXPECT_EQ(reader.readWholeFileAfter(7, 1), "ine 2\nLine 3");

    EXPECT_EQ(reader.readWholeFileBefore(8, 1), "Line 0\nLine 1\nLine 2\nL");
    EXPECT_EQ(reader.readWholeFileAfter(8, 1), "ine 3");
}

TEST_F(DocumentContextReaderTest, testFindCopyrightSingleLine)
{
    auto reader = createTestReader("/* Copyright (C) 2024 */\nCode line 0\nCode line 1");

    auto info = reader.findCopyright();
    ASSERT_TRUE(info.found);
    EXPECT_EQ(info.startLine, 0);
    EXPECT_EQ(info.endLine, 0);
}

TEST_F(DocumentContextReaderTest, testFindCopyrightMultiLine)
{
    auto reader = createTestReader(
        "/*\n * Copyright (C) 2024\n * \n * This file is part of QodeAssist.\n */\nCode line 0");

    auto info = reader.findCopyright();
    ASSERT_TRUE(info.found);
    EXPECT_EQ(info.startLine, 0);
    EXPECT_EQ(info.endLine, 4);
}

TEST_F(DocumentContextReaderTest, testFindCopyrightMultipleBlocks)
{
    auto reader = createTestReader("/* Copyright 2023 */\n\n/* Copyright 2024 */\nCode");

    auto info = reader.findCopyright();
    ASSERT_TRUE(info.found);
    EXPECT_EQ(info.startLine, 0);
    EXPECT_EQ(info.endLine, 0);
}

TEST_F(DocumentContextReaderTest, testFindCopyrightNoCopyright)
{
    auto reader = createTestReader("/* Just a comment */\nCode line 0");

    auto info = reader.findCopyright();
    ASSERT_TRUE(!info.found);
    EXPECT_EQ(info.startLine, -1);
    EXPECT_EQ(info.endLine, -1);
}

TEST_F(DocumentContextReaderTest, testGetContextBetween)
{
    auto reader = createTestReader("Line 0\nLine 1\nLine 2\nLine 3\nLine 4");

    EXPECT_EQ(reader.getContextBetween(2, -1, 0, -1), "");
    EXPECT_EQ(reader.getContextBetween(0, -1, 0, -1), "Line 0");
    EXPECT_EQ(reader.getContextBetween(1, -1, 1, -1), "Line 1");
    EXPECT_EQ(reader.getContextBetween(1, 3, 1, 1), "");
    EXPECT_EQ(reader.getContextBetween(1, 3, 1, 3), "");
    EXPECT_EQ(reader.getContextBetween(1, 3, 1, 4), "e");

    EXPECT_EQ(reader.getContextBetween(1, -1, 3, -1), "Line 1\nLine 2\nLine 3");
    EXPECT_EQ(reader.getContextBetween(1, 2, 3, -1), "ne 1\nLine 2\nLine 3");
    EXPECT_EQ(reader.getContextBetween(1, -1, 3, 2), "Line 1\nLine 2\nLi");
    EXPECT_EQ(reader.getContextBetween(1, 2, 3, 2), "ne 1\nLine 2\nLi");
}

TEST_F(DocumentContextReaderTest, testPrepareContext)
{
    auto reader = createTestReader("Line 0\nLine 1\nLine 2\nLine 3\nLine 4");

    EXPECT_EQ(
        reader.prepareContext(2, 3, *createSettingsForWholeFile()),
        (QodeAssist::PluginLLMCore::ContextData{
            .prefix = "Line 0\nLine 1\nLin",
            .suffix = "e 2\nLine 3\nLine 4",
            .fileContext = "\n Language:  (MIME: text/python) filepath: /path/to/file()\n\n"
                           "Recent Project Changes Context:\n "}));

    EXPECT_EQ(
        reader.prepareContext(2, 3, *createSettingsForLines(1, 1)),
        (QodeAssist::PluginLLMCore::ContextData{
            .prefix = "Line 1\nLin",
            .suffix = "e 2\nLine 3",
            .fileContext = "\n Language:  (MIME: text/python) filepath: /path/to/file()\n\n"
                           "Recent Project Changes Context:\n "}));

    EXPECT_EQ(
        reader.prepareContext(2, 3, *createSettingsForLines(2, 2)),
        (QodeAssist::PluginLLMCore::ContextData{
            .prefix = "Line 0\nLine 1\nLin",
            .suffix = "e 2\nLine 3\nLine 4",
            .fileContext = "\n Language:  (MIME: text/python) filepath: /path/to/file()\n\n"
                           "Recent Project Changes Context:\n "}));
}

#include "DocumentContextReaderTest.moc"
