/*
 * Copyright (C) 2025 Povilas Kanapickas
 *
 * This file is part of QodeAssist.
 *
 * QodeAssist is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * QodeAssist is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
 */

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

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

using namespace QodeAssist::Context;
using namespace QodeAssist::LLMCore;
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()),
        (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)),
        (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)),
        (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"