#include "mainwindow.h"
#include "uimanager.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QCheckBox>
#include <QMessageBox>
#include <QFileDialog>
#include <QSettings>
#include <QListWidget>
#include <QFontDialog>
#include <QColorDialog>
#include <QInputDialog>
#include <QMenu>
#include <QScrollBar>
#include <QTextCursor>
#include <QApplication>
#include <QToolBar>
#include <QGroupBox>
#include <QIcon>
#include <QDebug>
#include <QtConcurrent/QtConcurrent>
#include <yaml-cpp/yaml.h>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <QPlainTextEdit>
#include <QTextEdit>
#include <QVBoxLayout>
#include <QDialog>
#include <QPushButton>
#include <QFileInfo>
#include <QSettings>
#include <QMessageBox>
#include <QDebug>
#include <QTextBlockUserData>
#include <QPainter>
#include <QScrollBar>
#include <QIcon>
#include <QMenuBar>
#include <QMenu>
#include <QElapsedTimer>
#include <QMutex>
#include <QTimer>
#include <QThread>
#include <libssh/libssh.h>
#include <QTableWidget>
#include <QHeaderView>
#include <QTableWidgetItem>


class LogHighlighter : public QSyntaxHighlighter
{
public:
    LogHighlighter(QTextDocument *parent = nullptr) : QSyntaxHighlighter(parent)
    {
        HighlightingRule rule;

        // Статусы
        addRule("ok=([1-9]\\d*)", Qt::green);
        addRule("changed=([1-9]\\d*)", Qt::yellow);
        addRule("failed=([1-9]\\d*)", Qt::red);
        addRule("unreachable=([1-9]\\d*)", Qt::magenta, true);

        // Сообщения об ошибках
        addRule("FAILED!", Qt::red, true);
        addRule("fatal:", Qt::red, true);

        // IP адреса
        addRule("ok: \\[([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)\\]", Qt::green);
        addRule("fatal: \\[([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)\\]", Qt::red);
    }

protected:
    void highlightBlock(const QString &text) override
    {
        for (const HighlightingRule &rule : qAsConst(highlightingRules)) {
            QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);
            while (matchIterator.hasNext()) {
                QRegularExpressionMatch match = matchIterator.next();
                QTextCharFormat format = rule.format;

                setFormat(match.capturedStart(), match.capturedLength(), format);
            }
        }
    }

private:
    struct HighlightingRule
    {
        QRegularExpression pattern;
        QTextCharFormat format;
    };
    QVector<HighlightingRule> highlightingRules;

    void addRule(const QString &pattern, const QColor &color, bool bold = false)
    {
        HighlightingRule rule;
        rule.pattern = QRegularExpression(pattern);
        rule.format.setForeground(color);
        if (bold) {
            rule.format.setFontWeight(QFont::Bold);
        }
        highlightingRules.append(rule);
    }
};

class YamlHighlighter : public QSyntaxHighlighter
{
public:
    YamlHighlighter(QTextDocument *parent = nullptr) : QSyntaxHighlighter(parent)
    {
        // Комментарии
        addRule("^#.*$", Qt::gray);

        // Списки
        addRule("^\\s*-\\s+.*$", Qt::darkGreen);

        // Ключи
        addRule("^\\s*[^:#]+:\\s*.*$", Qt::blue);

        // Строки в кавычках
        addRule("'[^']*'", Qt::red);
        addRule("\"[^\"]*\"", Qt::red);

        // Числа
        addRule("\\b\\d+(\\.\\d+)?\\b", Qt::magenta);

        // Логические значения
        addRule("\\b(true|false|yes|no|on|off)\\b", Qt::cyan);

        // Специальные символы
        addRule("[&*!|><=]\\s*$", Qt::darkYellow);

        // Значения
        addRule(":\\s*[^#\n]+$", Qt::darkMagenta);
    }

protected:
    void highlightBlock(const QString &text) override
    {
        for (const HighlightingRule &rule : qAsConst(highlightingRules)) {
            QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);
            while (matchIterator.hasNext()) {
                QRegularExpressionMatch match = matchIterator.next();
                setFormat(match.capturedStart(), match.capturedLength(), rule.format);
            }
        }
    }

private:
    struct HighlightingRule
    {
        QRegularExpression pattern;
        QTextCharFormat format;
    };
    QVector<HighlightingRule> highlightingRules;

    void addRule(const QString &pattern, const QColor &color, bool bold = false)
    {
        HighlightingRule rule;
        rule.pattern = QRegularExpression(pattern);
        rule.format.setForeground(color);
        if (bold) {
            rule.format.setFontWeight(QFont::Bold);
        }
        highlightingRules.append(rule);
    }
};
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
    m_sshSession(nullptr),
    m_sftpSession(nullptr),
    m_useSudoEnabled(false),
    m_verboseEnabled(false),
    m_clearLogOnRunEnabled(true),
    m_checkModeEnabled(false),
    m_maxLogLines(10000),
    m_isPlaybookRunning(false)
{
    createThemeActions();

    // Создаем меню "Вид" и "Настройки"
    QMenuBar *menuBar = new QMenuBar(this);
    setMenuBar(menuBar);

    QMenu *viewMenu = menuBar->addMenu(tr("Тема"));
    viewMenu->addAction(lightThemeAction);
    viewMenu->addAction(darkThemeAction);
    viewMenu->addAction(blueThemeAction);

    QAction *settingsAction = new QAction(tr("Параметры"), this);
    connect(settingsAction, &QAction::triggered, this, &MainWindow::showOptions);
    menuBar->addAction(settingsAction);

    QAction *aboutAction = new QAction(tr("О программе"), this);
    connect(aboutAction, &QAction::triggered, this, &MainWindow::showAbout);
    menuBar->addAction(aboutAction);

    setWindowTitle("Ansible Playbook Launcher");
    // Устанавливаем иконку для главного окна
    QIcon appIcon = QIcon::fromTheme("ansiblegui");
    if (appIcon.isNull()) {
        appIcon.addFile("/usr/share/icons/hicolor/16x16/apps/ansiblegui.png", QSize(16, 16));
        appIcon.addFile("/usr/share/icons/hicolor/32x32/apps/ansiblegui.png", QSize(32, 32));
        appIcon.addFile("/usr/share/icons/hicolor/64x64/apps/ansiblegui.png", QSize(64, 64));
    }
    setWindowIcon(appIcon);
    setupUI();
    setupServerConnectionToggle();
    loadSettings();
    setupLogger();
    setupSSHConnection();
    connect(qApp, &QApplication::aboutToQuit, this, &MainWindow::disconnectFromServer);
    connect(this, &MainWindow::logAppended, this, &MainWindow::appendToLog, Qt::QueuedConnection);
    connect(this, &MainWindow::playbookExecutionFinished, this, &MainWindow::playbookFinished, Qt::QueuedConnection);

    connect(&m_logUpdateTimer, &QTimer::timeout, this, &MainWindow::processLogBuffer);
    m_logUpdateTimer.start(100); // Обновление каждые 100 мс

    connect(&m_logThread, &QThread::started, this, &MainWindow::startLogThread);
    connect(&m_logThread, &QThread::finished, this, &MainWindow::stopLogThread);

    // Создаем контекстное меню
    m_contextMenu = new QMenu(this);
    QAction *addPlaybookAction = new QAction("Добавить плейбук", this);
    QAction *deletePlaybookAction = new QAction("Удалить плейбук", this);
    m_contextMenu->addAction(addPlaybookAction);
    m_contextMenu->addAction(deletePlaybookAction);

    connect(addPlaybookAction, &QAction::triggered, this, &MainWindow::addNewPlaybook);
    connect(deletePlaybookAction, &QAction::triggered, this, &MainWindow::deletePlaybook);

    // Подключаем сигнал customContextMenuRequested к слоту showContextMenu
    m_playbookTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(m_playbookTreeView, &QTreeView::customContextMenuRequested, this, &MainWindow::showContextMenu);
}

MainWindow::~MainWindow()
{
    qDebug() << "Entering MainWindow destructor";
    saveSettings();
    disconnectFromServer();
    stopLogThread();
    qDebug() << "Exiting MainWindow destructor";
}

void MainWindow::closeEvent(QCloseEvent *event)
{
    saveSettings();
    QMainWindow::closeEvent(event);
}

void MainWindow::disconnectFromServer()
{
    qDebug() << "Entering disconnectFromServer";
    if (m_sftpSession) {
        sftp_free(m_sftpSession);
        m_sftpSession = nullptr;
        qDebug() << "SFTP session closed";
    }
    if (m_sshSession) {
        if (ssh_is_connected(m_sshSession)) {
            ssh_disconnect(m_sshSession);
            qDebug() << "SSH disconnected";
        }
        ssh_free(m_sshSession);
        m_sshSession = nullptr;
        qDebug() << "SSH session freed";
    }
    qDebug() << "Exiting disconnectFromServer";
}

void MainWindow::updateTheme()
{
    QPalette p = m_logArea->palette();
    if (m_isDarkTheme) {
        p.setColor(QPalette::Base, QColor(30, 30, 30));
        p.setColor(QPalette::Text, m_defaultTextColor);
    } else {
        p.setColor(QPalette::Base, Qt::white);
        p.setColor(QPalette::Text, Qt::black);
    }
    m_logArea->setPalette(p);
}

void MainWindow::setupUI()
{
    qDebug() << "Вход в setupUI";

    // Основной виджет и layout
    QWidget *centralWidget = new QWidget(this);
    setCentralWidget(centralWidget);
    QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);

    // Основной сплиттер
    m_mainSplitter = new QSplitter(Qt::Horizontal, centralWidget);
    mainLayout->addWidget(m_mainSplitter);

    // Левая боковая панель (список плейбуков)
    QWidget *playbookWidget = new QWidget(m_mainSplitter);
    QVBoxLayout *playbookLayout = new QVBoxLayout(playbookWidget);

    QLineEdit *searchPlaybooks = new QLineEdit(playbookWidget);
    searchPlaybooks->setPlaceholderText("Поиск плейбуков...");
    playbookLayout->addWidget(searchPlaybooks);

    m_playbookModel = new QStandardItemModel(this);
    m_proxyModel = new QSortFilterProxyModel(this);
    m_proxyModel->setSourceModel(m_playbookModel);
    m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
    m_proxyModel->setRecursiveFilteringEnabled(true);

    m_playbookTreeView = new QTreeView(playbookWidget);
    m_playbookTreeView->setHeaderHidden(true);
    m_playbookTreeView->setAnimated(true);
    m_playbookTreeView->setIndentation(20);
    m_playbookTreeView->setModel(m_proxyModel);
    connect(m_playbookTreeView, &QTreeView::clicked, this, &MainWindow::onPlaybookSelected);
    connect(m_playbookTreeView, &QTreeView::doubleClicked, this, &MainWindow::onPlaybookDoubleClicked);
    playbookLayout->addWidget(m_playbookTreeView);

    connect(searchPlaybooks, &QLineEdit::textChanged, this, &MainWindow::filterPlaybooks);

    // Основная область (справа от списка плейбуков)
    QWidget *mainAreaWidget = new QWidget(m_mainSplitter);
    QVBoxLayout *mainAreaLayout = new QVBoxLayout(mainAreaWidget);

    // Группа элементов управления плейбуком
    QGroupBox *playbookControlsGroup = new QGroupBox("Управление плейбуком", mainAreaWidget);
    QGridLayout *controlsLayout = new QGridLayout(playbookControlsGroup);

    controlsLayout->addWidget(new QLabel("Пароль sudo/runas:"), 0, 0);
    m_sudoPassword = new QLineEdit(playbookControlsGroup);
    m_sudoPassword->setEchoMode(QLineEdit::Password);
    m_sudoPassword->setEnabled(true); // Update: Added this line
    controlsLayout->addWidget(m_sudoPassword, 0, 1);

    QPushButton* kinitButton = new QPushButton("Выдать билет", playbookControlsGroup);
    connect(kinitButton, &QPushButton::clicked, this, &MainWindow::performKinit);
    controlsLayout->addWidget(kinitButton, 0, 2);

    controlsLayout->addWidget(new QLabel("Ограничение хостов:"), 1, 0);
    m_limit = new QLineEdit(playbookControlsGroup);
    m_limit->installEventFilter(this);
    m_limit->setPlaceholderText("Введите хосты или группы ( linux, windows, 10.10.0.12, 10.10.0.[12:15], 10.10.*.12 ) и нажмите Enter");
    controlsLayout->addWidget(m_limit, 1, 1, 1, 2);

    controlsLayout->addWidget(new QLabel("Теги:"), 2, 0);
    m_tags = new QLineEdit(playbookControlsGroup);
    controlsLayout->addWidget(m_tags, 2, 1);

    QPushButton *selectTagsButton = new QPushButton("Выбрать теги", playbookControlsGroup);
    connect(selectTagsButton, &QPushButton::clicked, this, &MainWindow::selectTags);
    controlsLayout->addWidget(selectTagsButton, 2, 2);

    controlsLayout->addWidget(new QLabel("Extra vars:"), 3, 0);
    m_extraVars = new QLineEdit(playbookControlsGroup);
    m_extraVars->setPlaceholderText("Введите дополнительные переменные (например: var1=value1 var2=value2)");
    controlsLayout->addWidget(m_extraVars, 3, 1);

    QPushButton *selectVarsButton = new QPushButton(" Выбрать переменные ", playbookControlsGroup);
    connect(selectVarsButton, &QPushButton::clicked, this, &MainWindow::selectExtraVars);
    controlsLayout->addWidget(selectVarsButton, 3, 2);

    m_runButton = new QPushButton("Запустить Playbook (F5)", playbookControlsGroup);
    connect(m_runButton, &QPushButton::clicked, this, &MainWindow::runPlaybook);
    controlsLayout->addWidget(m_runButton, 4, 0, 1, 2);

    m_stopButton = new QPushButton(" Остановить ", playbookControlsGroup);
    connect(m_stopButton, &QPushButton::clicked, this, &MainWindow::stopPlaybook);
    m_stopButton->setEnabled(false);
    controlsLayout->addWidget(m_stopButton, 4, 2);

    mainAreaLayout->addWidget(playbookControlsGroup);

    // Группа лога
    QGroupBox *logGroup = new QGroupBox("Лог выполнения", mainAreaWidget);
    QVBoxLayout *logLayout = new QVBoxLayout(logGroup);

    m_logArea = new QTextEdit(this); // Using QTextEdit for now, can be changed to QPlainTextEdit later
    m_logArea->setReadOnly(true);
    m_logArea->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
    m_logArea->setLineWrapMode(QTextEdit::NoWrap);
    m_logArea->setWordWrapMode(QTextOption::NoWrap);
    m_logArea->document()->setMaximumBlockCount(10000);
    logLayout->addWidget(m_logArea);

    // Создаем и применяем LogHighlighter к m_logArea
    new LogHighlighter(m_logArea->document());

    // Элементы управления фильтром
    QHBoxLayout *filterLayout = new QHBoxLayout();
    filterLayout->addWidget(new QLabel("Фильтр:"));
    m_filterInput = new QLineEdit(logGroup);
    m_filterInput->setPlaceholderText("Введите текст для фильтрации и нажмите Enter");
    m_filterInput->installEventFilter(this);
    filterLayout->addWidget(m_filterInput);
    filterLayout->addWidget(new QLabel("Строк после:"));
    m_linesAfter = new QSpinBox(logGroup);
    m_linesAfter->setRange(0, 10);
    filterLayout->addWidget(m_linesAfter);

    QPushButton *changeBgButton = new QPushButton("Сменить фон", logGroup);
    connect(changeBgButton, &QPushButton::clicked, this, [this]() {
        m_isDarkTheme = !m_isDarkTheme;
        updateTheme();
    });
    filterLayout->addWidget(changeBgButton);

    logLayout->addLayout(filterLayout);

    mainAreaLayout->addWidget(logGroup);

    // Подключение к серверу
    QWidget *serverConnectionWidget = new QWidget(mainAreaWidget);
    QGridLayout* serverLayout = new QGridLayout(serverConnectionWidget);

    // Создаем кнопку-переключатель
    m_toggleButton = new QPushButton("Параметры подключения", this);
    m_toggleButton->setIcon(QIcon::fromTheme("go-down"));
    connect(m_toggleButton, &QPushButton::clicked, this, &MainWindow::toggleServerConnectionParams);
    mainAreaLayout->addWidget(m_toggleButton);

    serverLayout->addWidget(new QLabel("Хост:"), 1, 0);
    m_serverHostInput = new QLineEdit(serverConnectionWidget);
    serverLayout->addWidget(m_serverHostInput, 1, 1);
    serverLayout->addWidget(new QLabel("Порт:"), 1, 2);
    m_serverPortInput = new QSpinBox(serverConnectionWidget);
    m_serverPortInput->setRange(1, 65535);
    m_serverPortInput->setValue(22);
    serverLayout->addWidget(m_serverPortInput, 1, 3);
    serverLayout->addWidget(new QLabel("Пользователь:"), 2, 0);
    m_serverUsernameInput = new QLineEdit(serverConnectionWidget);
    serverLayout->addWidget(m_serverUsernameInput, 2, 1);
    serverLayout->addWidget(new QLabel("Пароль:"), 2, 2);
    m_serverPasswordInput = new QLineEdit(serverConnectionWidget);
    m_serverPasswordInput->setEchoMode(QLineEdit::Password);
    serverLayout->addWidget(m_serverPasswordInput, 2, 3);
    QPushButton* connectButton = new QPushButton("Подключиться", serverConnectionWidget);
    connect(connectButton, &QPushButton::clicked, this, &MainWindow::connectToServer);
    serverLayout->addWidget(connectButton, 3, 0, 1, 4);

    mainAreaLayout->addWidget(serverConnectionWidget);

    m_serverPasswordInput->installEventFilter(this);

    // Настройки по умолчанию
    m_runButton->setEnabled(false);
    m_stopButton->setEnabled(false);

    // Настройки темы по умолчанию
    m_isDarkTheme = true;
    m_defaultTextColor = Qt::white;
    updateTheme();

    // Инициализация анимации для serverConnectionWidget
    m_serverConnectionWidget = serverConnectionWidget;
    m_animation = new QPropertyAnimation(m_serverConnectionWidget, "maximumHeight");
    m_animation->setDuration(300);
    m_animation->setEasingCurve(QEasingCurve::InOutQuad);
    m_serverConnectionWidgetHeight = m_serverConnectionWidget->sizeHint().height();
    m_serverConnectionWidget->setMaximumHeight(m_serverConnectionWidgetHeight);

    // Восстановление размеров окна и состояния сплиттера
    resize(1024, 768);
    QSettings settings(QCoreApplication::applicationDirPath() + "/settings.ini", QSettings::IniFormat);
    restoreGeometry(settings.value("windowGeometry").toByteArray());
    m_mainSplitter->restoreState(settings.value("splitterState").toByteArray());

    // Добавляем валидацию формата ввода
    connect(m_extraVars, &QLineEdit::textChanged, this, &MainWindow::validateExtraVars);

    qDebug() << "Выход из setupUI";
}

void MainWindow::setupServerConnectionToggle()
{
    connect(m_toggleButton, &QPushButton::clicked, this, &MainWindow::toggleServerConnectionParams);

    m_serverConnectionWidgetHeight = m_serverConnectionWidget->sizeHint().height();
    m_animation = new QPropertyAnimation(m_serverConnectionWidget, "maximumHeight");
    m_animation->setDuration(300);
    m_animation->setEasingCurve(QEasingCurve::InOutQuad);

    // Изначально показываем параметры подключения
    m_serverConnectionWidget->setMaximumHeight(m_serverConnectionWidgetHeight);
    m_toggleButton->setIcon(QIcon::fromTheme("go-up"));
}

void MainWindow::toggleServerConnectionParams()
{
    if (m_serverConnectionWidget->maximumHeight() > 0) {
        // Скрываем параметры
        m_animation->setStartValue(m_serverConnectionWidgetHeight);
        m_animation->setEndValue(0);
        m_toggleButton->setIcon(QIcon::fromTheme("go-down"));
    } else {
        // Показываем параметры
        m_animation->setStartValue(0);
        m_animation->setEndValue(m_serverConnectionWidgetHeight);
        m_toggleButton->setIcon(QIcon::fromTheme("go-up"));
    }
    m_animation->start();
}

void MainWindow::loadSettings()
{
    qDebug() << "Entering loadSettings";
    QSettings settings(QCoreApplication::applicationDirPath() + "/settings.ini", QSettings::IniFormat);

    // Загружаем тему
    m_currentTheme = static_cast<UIManager::Theme>(settings.value("currentTheme", static_cast<int>(UIManager::Light)).toInt());
    UIManager::instance().setTheme(m_currentTheme);

    // Загружаем путь к плейбукам по умолчанию
    m_defaultPlaybookPath = settings.value("defaultPlaybookPath", "/home/ansible").toString();

    restoreGeometry(settings.value("windowGeometry").toByteArray());

    // Загружаем последний использованный плейбук
    m_lastUsedPlaybook = settings.value("lastPlaybook").toString();

    m_limit->setText(settings.value("lastLimit").toString());
    m_tags->setText(settings.value("lastTags").toString());
    m_extraVars->setText(settings.value("lastExtraVars").toString());

    QFont font = m_logArea->font();
    font.fromString(settings.value("logFont", font.toString()).toString());
    m_logArea->setFont(font);

    QPalette palette = m_logArea->palette();
    palette.setColor(QPalette::Base, settings.value("logBackgroundColor", palette.color(QPalette::Base)).value<QColor>());
    palette.setColor(QPalette::Text, settings.value("logTextColor", palette.color(QPalette::Text)).value<QColor>());
    m_logArea->setPalette(palette);

    // Загружаем настройки подключения к серверу
    m_serverHostInput->setText(settings.value("serverHost").toString());
    m_serverPortInput->setValue(settings.value("serverPort", 22).toInt());
    m_serverUsernameInput->setText(settings.value("serverUsername").toString());
    m_useSudoEnabled = settings.value("useSudo", false).toBool();
    m_verboseEnabled = settings.value("verbose", false).toBool();
    m_clearLogOnRunEnabled = settings.value("clearLogOnRun", true).toBool();
    m_checkModeEnabled = settings.value("checkMode", false).toBool();

    // Загрузка размера окна редактирования плейбука
    m_editDialogSize = settings.value("editDialogSize", QSize(800, 600)).toSize();

    applySettings();

    // Если есть подключение к серверу, загружаем плейбуки из директории по умолчанию
    if (m_sshSession && m_sftpSession) {
        updatePlaybookList();

        // Устанавливаем последний использованный плейбук, если он доступен
        if (!m_lastUsedPlaybook.isEmpty()) {
            selectPlaybookInTree(m_lastUsedPlaybook);
        }
    }
}

void MainWindow::setupLogger()
{
    qDebug() << "Entering setupLogger";
    try {
        auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("ansible_gui.log", true);
        auto logger = std::make_shared<spdlog::logger>("ansible_gui", file_sink);
        spdlog::set_default_logger(logger);
        spdlog::set_level(spdlog::level::debug);
        spdlog::flush_on(spdlog::level::debug);
    } catch (const spdlog::spdlog_ex& ex) {
        QMessageBox::warning(this, "Ошибка", QString("Не удалось настроить логгер: %1").arg(ex.what()));
    }
}

void MainWindow::setupSSHConnection()
{
    qDebug() << "Entering setupSSHConnection";
    ssh_init();
    m_keepAliveTimer = new QTimer(this);
    connect(m_keepAliveTimer, &QTimer::timeout, this, &MainWindow::keepAliveSSH);
    m_keepAliveTimer->start(60000); // Отправка keep-alive каждую минуту
}

void MainWindow::keepAliveSSH()
{
    qDebug() << "Entering keepAliveSSH";
    if (m_sshSession && ssh_is_connected(m_sshSession)) {
        int rc = ssh_send_ignore(m_sshSession, "keep-alive");
        if (rc != SSH_OK) {
            qDebug() << "Failed to send keep-alive packet:" << ssh_get_error(m_sshSession);
            // Попытка переподключения
            if (ssh_connect(m_sshSession) != SSH_OK) {
                qDebug() << "Failed to reconnect:" << ssh_get_error(m_sshSession);
                // Здесь можно добавить код для обработки потери соединения
            } else {
                qDebug() << "Successfully reconnected";
            }
        } else {
            qDebug() << "Keep-alive packet sent successfully";
        }
    } else {
        qDebug() << "SSH session is not connected";
        // Здесь можно добавить код для обработки отсутствия соединения
    }
}

void MainWindow::selectPlaybookInTree(const QString& playbookPath)
{
    QModelIndex sourceIndex = findPlaybookInModel(m_playbookModel->invisibleRootItem(), playbookPath);
    if (sourceIndex.isValid()) {
        QModelIndex proxyIndex = m_proxyModel->mapFromSource(sourceIndex);
        m_playbookTreeView->setCurrentIndex(proxyIndex);
        m_playbookTreeView->scrollTo(proxyIndex);
        onPlaybookSelected(proxyIndex);
    }
}

QModelIndex MainWindow::findPlaybookInModel(QStandardItem* parentItem, const QString& playbookPath)
{
    if (parentItem->data(Qt::UserRole).toString() == playbookPath) {
        return parentItem->index();
    }

    for (int i = 0; i < parentItem->rowCount(); ++i) {
        QStandardItem* childItem = parentItem->child(i);
        QModelIndex result = findPlaybookInModel(childItem, playbookPath);
        if (result.isValid()) {
            return result;
        }
    }

    return QModelIndex();
}

void MainWindow::connectToServer()
{
    qDebug() << "Entering connectToServer";
    // Сначала отключаемся от предыдущего соединения
    disconnectFromServer();
    QString host = m_serverHostInput->text();
    int port = m_serverPortInput->value();
    QString username = m_serverUsernameInput->text();
    QString password = m_serverPasswordInput->text();

    m_sshSession = ssh_new();
    if (m_sshSession == nullptr) {
        appendToLog("Ошибка: Не удалось создать SSH сессию");
        return;
    }

    ssh_options_set(m_sshSession, SSH_OPTIONS_HOST, host.toStdString().c_str());
    ssh_options_set(m_sshSession, SSH_OPTIONS_PORT, &port);
    ssh_options_set(m_sshSession, SSH_OPTIONS_USER, username.toStdString().c_str());

    int rc = ssh_connect(m_sshSession);
    if (rc != SSH_OK) {
        appendToLog("Ошибка: Не удалось подключиться к серверу: " + QString(ssh_get_error(m_sshSession)));
        ssh_free(m_sshSession);
        m_sshSession = nullptr;
        return;
    }

    rc = ssh_userauth_password(m_sshSession, nullptr, password.toStdString().c_str());
    if (rc != SSH_AUTH_SUCCESS) {
        appendToLog("Ошибка: Аутентификация не удалась: " + QString(ssh_get_error(m_sshSession)));
        ssh_disconnect(m_sshSession);
        ssh_free(m_sshSession);
        m_sshSession = nullptr;
        return;
    }

    m_sftpSession = sftp_new(m_sshSession);
    if (m_sftpSession == nullptr) {
        appendToLog("Ошибка: Не удалось создать SFTP сессию");
        ssh_disconnect(m_sshSession);
        ssh_free(m_sshSession);
        m_sshSession = nullptr;
        return;
    }

    rc = sftp_init(m_sftpSession);
    if (rc != SSH_OK) {
        appendToLog("Ошибка: Не удалось инициализировать SFTP сессию: " + QString(ssh_get_error(m_sshSession)));
        sftp_free(m_sftpSession);
        ssh_disconnect(m_sshSession);
        ssh_free(m_sshSession);
        m_sshSession = nullptr;
        m_sftpSession = nullptr;
        return;
    }

    appendToLog("Подключение к серверу выполнено успешно\n");
    updatePlaybookList();

    m_runButton->setEnabled(true);
    m_stopButton->setEnabled(false);

    collapseConnectionParams();
}

void MainWindow::updatePlaybookList()
{
    qDebug() << "Entering updatePlaybookList";
    if (m_sshSession == nullptr || m_sftpSession == nullptr) {
        QMessageBox::warning(this, "Предупреждение", "Нет подключения к серверу");
        return;
    }

    // Загружаем путь к директории с плейбуками из настроек
    QSettings settings(QCoreApplication::applicationDirPath() + "/settings.ini", QSettings::IniFormat);
    QString path = settings.value("defaultPlaybookPath", "/home/ansible").toString();

    m_playbookModel->clear();
    QStandardItem *rootItem = m_playbookModel->invisibleRootItem();

    if (loadPlaybooksFromPath(path, rootItem)) {
        m_playbookTreeView->expandAll();

        // Устанавливаем последний использованный плейбук, если он доступен
        if (!m_lastUsedPlaybook.isEmpty()) {
            selectPlaybookInTree(m_lastUsedPlaybook);
        }
    } else {
        QMessageBox::warning(this, "Предупреждение", "В указанной директории не найдено плейбуков");
    }

    qDebug() << "Exiting updatePlaybookList";
}

void MainWindow::editPlaybook()
{
    qDebug() << "Entering editPlaybook";
    // Update 5: Check if playbook is running
    if (m_isPlaybookRunning) {
        QMessageBox::warning(this, "Предупреждение", "Нельзя редактировать плейбук во время выполнения");
        return;
    }
    if (m_sshSession == nullptr || m_sftpSession == nullptr) {
        QMessageBox::warning(this, "Предупреждение", "Нет подключения к серверу");
        return;
    }

    QString playbookPath = m_selectedPlaybook;
    if (playbookPath.isEmpty()) {
        QMessageBox::warning(this, "Предупреждение", "Плейбук не выбран");
        return;
    }

    qDebug() << "Attempting to open playbook:" << playbookPath;

    sftp_file file = sftp_open(m_sftpSession, playbookPath.toStdString().c_str(), O_RDONLY, 0);
    if (file == nullptr) {
        QString errorMsg = QString("Не удалось открыть файл: %1").arg(ssh_get_error(m_sshSession));
        QMessageBox::critical(this, "Ошибка", errorMsg);
        qDebug() << errorMsg;
        sftp_close(file);
        return;
    }

    QByteArray content;
    char buffer[4096];
    ssize_t nbytes;
    while ((nbytes = sftp_read(file, buffer, sizeof(buffer))) > 0) {
        content.append(buffer, nbytes);
    }

    if (nbytes < 0) {
        QString errorMsg = QString("Ошибка при чтении файла: %1").arg(ssh_get_error(m_sshSession));
        QMessageBox::critical(this, "Ошибка", errorMsg);
        qDebug() << errorMsg;
        sftp_close(file);
        return;
    }

    sftp_close(file);

    QDialog* editDialog = new QDialog(this);
    editDialog->setWindowTitle("Редактирование плейбука: " + QFileInfo(playbookPath).fileName());

    QSettings settings(QCoreApplication::applicationDirPath() + "/settings.ini", QSettings::IniFormat);
    QSize dialogSize = settings.value("editDialogSize", QSize(800, 600)).toSize();
    editDialog->resize(dialogSize);

    QVBoxLayout* layout = new QVBoxLayout(editDialog);

    QTextEdit* editor = new QTextEdit(editDialog);
    editor->setPlainText(QString::fromUtf8(content));
    layout->addWidget(editor);

    // Create a new YamlHighlighter for this editor
    YamlHighlighter* yamlHighlighter = new YamlHighlighter(editor->document());

    QPushButton* saveButton = new QPushButton("Сохранить", editDialog);
    layout->addWidget(saveButton);

    connect(saveButton, &QPushButton::clicked, [this,editor, playbookPath, editDialog]() {
        QString content = editor->toPlainText();
        sftp_file file = sftp_open(m_sftpSession, playbookPath.toStdString().c_str(), O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
        if (file == nullptr) {
            QString errorMsg = QString("Не удалось открыть файл для записи: %1").arg(ssh_get_error(m_sshSession));
            QMessageBox::critical(this, "Ошибка", errorMsg);
            qDebug() << errorMsg;
            return;
        }

        QByteArray data = content.toUtf8();
        ssize_t nwritten = sftp_write(file, data.constData(), data.size());
        if (nwritten != data.size()) {
            QString errorMsg = QString("Ошибка при записи в файл: %1").arg(ssh_get_error(m_sshSession));
            QMessageBox::critical(this, "Ошибка", errorMsg);
            qDebug() << errorMsg;
            sftp_close(file);
            return;
        }

        sftp_close(file);
        QMessageBox::information(this, "Успех", "Плейбук успешно сохранен");
        editDialog->accept();
    });

    connect(editDialog, &QDialog::finished, [this, editDialog]() {
        QSettings settings(QCoreApplication::applicationDirPath() + "/settings.ini", QSettings::IniFormat);
        settings.setValue("editDialogSize", editDialog->size());
        qDebug() << "Saved edit dialog size on accept:" << editDialog->size();
    });

    editDialog->exec();
    delete editDialog;
}

void MainWindow::showOptions()
{
    QDialog* optionsDialog = new QDialog(this);
    optionsDialog->setWindowTitle("Настройки");

    QVBoxLayout* layout = new QVBoxLayout(optionsDialog);

    QCheckBox* verboseCheckBox = new QCheckBox("Подробный вывод (-v)", optionsDialog);
    QCheckBox* clearLogOnRunCheckBox = new QCheckBox("Очищать лог при запуске", optionsDialog);
    QCheckBox* checkModeCheckBox = new QCheckBox("Проверка (--check)", optionsDialog);

    verboseCheckBox->setChecked(m_verboseEnabled);
    clearLogOnRunCheckBox->setChecked(m_clearLogOnRunEnabled);
    checkModeCheckBox->setChecked(m_checkModeEnabled);

    layout->addWidget(verboseCheckBox);
    layout->addWidget(clearLogOnRunCheckBox);
    layout->addWidget(checkModeCheckBox);

    // Добавляем настройки шрифта и цвета
    QPushButton* fontButton = new QPushButton("Шрифт", optionsDialog);
    connect(fontButton, &QPushButton::clicked, this, &MainWindow::changeFont);
    layout->addWidget(fontButton);

    QPushButton* bgColorButton = new QPushButton("Цвет фона", optionsDialog);
    connect(bgColorButton, &QPushButton::clicked, this, &MainWindow::changeBackground);
    layout->addWidget(bgColorButton);

    QPushButton* fgColorButton = new QPushButton("Цвет текста", optionsDialog);
    connect(fgColorButton, &QPushButton::clicked, this, &MainWindow::changeForeground);
    layout->addWidget(fgColorButton);

    QPushButton* okButton = new QPushButton("OK", optionsDialog);
    connect(okButton, &QPushButton::clicked, optionsDialog, &QDialog::accept);
    layout->addWidget(okButton);

    if (optionsDialog->exec() == QDialog::Accepted) {
        m_verboseEnabled = verboseCheckBox->isChecked();
        m_clearLogOnRunEnabled = clearLogOnRunCheckBox->isChecked();
        m_checkModeEnabled = checkModeCheckBox->isChecked();

        applySettings();

        qDebug() << "Сохраненные и примененные настройки:";
        qDebug() << "verbose:" << m_verboseEnabled;
        qDebug() << "clearLogOnRun:" << m_clearLogOnRunEnabled;
        qDebug() << "checkMode:" << m_checkModeEnabled;
    }

    delete optionsDialog;
}

void MainWindow::applySettings()
{
    // Можно добавить дополнительную логику для других настроек, если необходимо

    qDebug() << "Настройки применены";
}

void MainWindow::runPlaybook()
{
    qDebug() << "Entering runPlaybook()";

    QSettings settings(QCoreApplication::applicationDirPath() + "/settings.ini", QSettings::IniFormat);
    settings.setValue("lastPlaybook", m_selectedPlaybook);

    if (!m_sshSession || !ssh_is_connected(m_sshSession)) {
        appendToLog("Ошибка: Нет подключения к серверу\n");
        return;
    }

    if (m_selectedPlaybook.isEmpty()) {
        appendToLog("Ошибка: Выберите playbook");
        return;
    }

    // Очищаем лог только если установлена соответствующая опция
    if (m_clearLogOnRunEnabled) {
        m_logArea->clear();
        m_fullLog.clear();
    }

    QString remotePlaybookPath = m_selectedPlaybook;

    QStringList arguments;
    arguments << remotePlaybookPath;

    QString sudoPassword = m_sudoPassword->text();
    if (!sudoPassword.isEmpty()) {
        arguments << "--extra-vars" << QString("ansible_become_pass=%1").arg(sudoPassword);
    }

    if (!m_extraVars->text().isEmpty()) {
        arguments << "--extra-vars" << QString("\"%1\"").arg(m_extraVars->text());
    }

    arguments << "-f" << "30";

    if (!m_limit->text().isEmpty()) {
        arguments << "-l" << QString("'%1'").arg(m_limit->text());
    }

    if (!m_tags->text().isEmpty()) {
        arguments << "--tags" << QString("'%1'").arg(m_tags->text());
    }

    if (m_verboseEnabled) {
        arguments << "-v";
    }

    if (m_checkModeEnabled) {
        arguments << "--check";
    }

    QString command = "ansible-playbook " + arguments.join(" ");

    // Маскируем пароль в логах
    QString logCommand = command;
    logCommand.replace(QRegularExpression("ansible_become_pass=[^ ]+"), "ansible_become_pass=******");
    appendToLog(logCommand + "\n");

    qDebug() << "Full command (with masked password):" << logCommand;
    qDebug() << "Sudo enabled:" << m_useSudoEnabled;

    try {
        ssh_channel channel = ssh_channel_new(m_sshSession);
        if (channel == nullptr) {
            throw std::runtime_error("Не удалось создать SSH канал");
        }
        qDebug() << "SSH channel created successfully";

        if (ssh_channel_open_session(channel) != SSH_OK) {
            ssh_channel_free(channel);
            throw std::runtime_error(("Не удалось открыть SSH сессию: " + QString(ssh_get_error(m_sshSession))).toStdString());
        }
        qDebug() << "SSH session opened successfully";

        qDebug() << "Executing command:" << logCommand;
        if (ssh_channel_request_exec(channel, command.toUtf8().constData()) != SSH_OK) {
            ssh_channel_close(channel);
            ssh_channel_free(channel);
            throw std::runtime_error(("Не удалось выполнить команду: " + QString(ssh_get_error(m_sshSession))).toStdString());
        }
        qDebug() << "Command execution requested successfully";

        m_isPlaybookRunning = true; // Update 2: Set m_isPlaybookRunning to true
        updateUIState();
        m_runButton->setEnabled(false);
        m_stopButton->setEnabled(true);

        // Создаем отдельный поток для чтения вывода
        QFuture<void> future = QtConcurrent::run([this, channel]() {
            char buffer[4096];
            int nbytes;
            while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0)) > 0) {
                QString output = QString::fromUtf8(buffer, nbytes);
                emit logAppended(output);
            }

            // Добавим чтение stderr
            while ((nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 1)) > 0) {
                QString error = QString::fromUtf8(buffer, nbytes);
                emit logAppended("ERROR: " + error);
            }

            if (nbytes < 0) {
                emit logAppended("Ошибка при чтении вывода команды: " + QString(ssh_get_error(m_sshSession)));
            }

            int exit_status = ssh_channel_get_exit_status(channel);
            ssh_channel_close(channel);
            ssh_channel_free(channel);

            emit playbookExecutionFinished(exit_status);
        });

        // Запускаем поток чтения лога
        m_logThread.start();

        // Ожидаем завершения выполнения в отдельном потоке
        m_futureWatcher.setFuture(future);
    } catch (const std::exception& e) {
        appendToLog("Критическая ошибка при запуске playbook: " + QString::fromStdString(e.what()));
        qDebug() << "Exception caught:" << e.what();
        m_runButton->setEnabled(true);
        m_stopButton->setEnabled(false);
    } catch (...) {
        appendToLog("Неизвестная критическая ошибка при запуске playbook");
        qDebug() << "Unknown exception caught";
        m_runButton->setEnabled(true);
        m_stopButton->setEnabled(false);
    }

    qDebug() << "Exiting runPlaybook()";
}

void MainWindow::playbookFinished(int exit_status)
{
    qDebug() << "Entering playbookFinished() with exit status:" << exit_status;

    m_isPlaybookRunning = false; // Update 3: Set m_isPlaybookRunning to false
    updateUIState();
    if (exit_status == 0) {
        appendToLog("\nPlaybook успешно выполнен!\n");
    } else {
        appendToLog(QString("\nПроизошла ошибка при выполнении playbook. Код возврата: %1").arg(exit_status));
    }

    m_runButton->setEnabled(true);
    m_stopButton->setEnabled(false);

    qDebug() << "Exiting playbookFinished()";
}

bool MainWindow::loadPlaybooksFromPath(const QString& path, QStandardItem* parentItem)
{
    qDebug() << "Loading playbooks from path:" << path;
    sftp_dir dir;
    sftp_attributes attributes;

    dir = sftp_opendir(m_sftpSession, path.toStdString().c_str());
    if (dir == nullptr) {
        QMessageBox::critical(this, "Ошибка", "Не удалось открыть директорию: " + QString(ssh_get_error(m_sshSession)));
        return false;
    }

    bool hasYmlFiles = false;
    QList<QStandardItem*> items;

    while ((attributes = sftp_readdir(m_sftpSession, dir)) != nullptr) {
        QString filename = QString(attributes->name);
        QString fullPath = QString("%1/%2").arg(path).arg(filename);

        if (filename != "." && filename != "..") {
            if (attributes->type == SSH_FILEXFER_TYPE_DIRECTORY) {
                QStandardItem *item = new QStandardItem(filename);
                item->setData(fullPath, Qt::UserRole);
                item->setEditable(false); // Отключаем редактирование
                items.append(item);
                if (loadPlaybooksFromPath(fullPath, item)) {
                    hasYmlFiles = true;
                } else {
                    items.removeLast();
                }
            } else if (filename.endsWith(".yml") || filename.endsWith(".yaml")) {
                QStandardItem *item = new QStandardItem(filename);
                item->setData(fullPath, Qt::UserRole);
                item->setEditable(false); // Отключаем редактирование
                items.append(item);
                hasYmlFiles = true;
            }
        }

        sftp_attributes_free(attributes);
    }

    sftp_closedir(dir);

    // Сортируем элементы по алфавиту
    std::sort(items.begin(), items.end(), [](QStandardItem* a, QStandardItem* b) {
        return a->text().toLower() < b->text().toLower();
    });

    // Добавляем отсортированные элементы в модель
    for (QStandardItem* item : items) {
        parentItem->appendRow(item);
    }

    return hasYmlFiles;
}

void MainWindow::stopPlaybook()
{
    qDebug() << "Entering stopPlaybook";

    m_stopButton->setEnabled(false);

    QTimer::singleShot(0, this, [this]() {
        ssh_session stop_session = nullptr;
        ssh_channel channel = nullptr;

        auto cleanup = [&]() {
            if (channel) {
                ssh_channel_close(channel);
                ssh_channel_free(channel);
            }
            if (stop_session) {
                ssh_disconnect(stop_session);
                ssh_free(stop_session);
            }
        };

        try {
            stop_session = ssh_new();
            if (!stop_session) throw std::runtime_error("Не удалось создать новую SSH-сессию");

            char* host = nullptr;
            char* user = nullptr;
            unsigned int port = 22;

            if (ssh_options_get(m_sshSession, SSH_OPTIONS_HOST, &host) != SSH_OK) {
                throw std::runtime_error("Не удалось получить хост");
            }
            if (ssh_options_get(m_sshSession, SSH_OPTIONS_USER, &user) != SSH_OK) {
                throw std::runtime_error("Не удалось получить пользователя");
            }
            if (ssh_options_get_port(m_sshSession, &port) != SSH_OK) {
                throw std::runtime_error("Не удалось получить порт");
            }

            if (ssh_options_set(stop_session, SSH_OPTIONS_HOST, host) != SSH_OK) {
                throw std::runtime_error("Не удалось установить хост");
            }
            if (ssh_options_set(stop_session, SSH_OPTIONS_USER, user) != SSH_OK) {
                throw std::runtime_error("Не удалось установить пользователя");
            }
            if (ssh_options_set(stop_session, SSH_OPTIONS_PORT, &port) != SSH_OK) {
                throw std::runtime_error("Не удалось установить порт");
            }

            if (ssh_connect(stop_session) != SSH_OK)
                throw std::runtime_error("Не удалось подключиться к серверу");

            // Используем тот же метод аутентификации, что и в основной сессии
            int rc = ssh_userauth_password(stop_session, nullptr, m_serverPasswordInput->text().toStdString().c_str());
            if (rc != SSH_AUTH_SUCCESS)
                throw std::runtime_error("Не удалось аутентифицироваться на сервере");

            channel = ssh_channel_new(stop_session);
            if (!channel) throw std::runtime_error("Не удалось создать SSH-канал");

            if (ssh_channel_open_session(channel) != SSH_OK)
                throw std::runtime_error("Не удалось открыть SSH-сессию");

            const char* command = "pkill -f '/usr/bin/ansible-playbook'";
            if (ssh_channel_request_exec(channel, command) != SSH_OK)
                throw std::runtime_error("Не удалось отправить запрос на остановку playbook");

            appendToLog("\nОтправлен запрос на остановку playbook.");

            char buffer[256];
            int nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
            if (nbytes > 0) {
                QString response = QString::fromUtf8(buffer, nbytes).trimmed();
                appendToLog("\nОтвет сервера: " + response);
            }

            ssh_channel_send_eof(channel);
        }
        catch (const std::exception& e) {
            appendToLog("\nОшибка: " + QString(e.what()));
        }

        cleanup();

        QMetaObject::invokeMethod(this, [this]() {
            m_runButton->setEnabled(true);
            m_stopButton->setEnabled(false);
            appendToLog("\nПроцесс остановки playbook завершен.\n");
        }, Qt::QueuedConnection);
    });
}

void MainWindow::updateLog()
{
    qDebug() << "Entering updateLog";

    // Читаем данные порциями
    const int maxSize = 1024 * 1024; // 1 MB
    QByteArray output = m_process->read(maxSize);
    QByteArray error = m_process->readAllStandardError(); // Обычно ошибки небольшие, читаем все

    if (!error.isEmpty()) {
        appendToLog("ERROR: " + QString::fromUtf8(error));
    }
    if (!output.isEmpty()) {
        appendToLog(QString::fromUtf8(output));
    }

    // Если есть еще данные, планируем следующее обновление
    if (m_process->bytesAvailable() > 0) {
        QTimer::singleShot(0, this, &MainWindow::updateLog);
    }
}

void MainWindow::appendToLog(const QString& text)
{
    QMutexLocker locker(&m_logMutex);
    m_logBuffer += text;
    m_fullLog += text;
}

void MainWindow::processLogBuffer()
{
    QMutexLocker locker(&m_logMutex);
    if (m_logBuffer.isEmpty()) {
        return;
    }

    QTextCursor cursor(m_logArea->document());
    cursor.movePosition(QTextCursor::End);
    cursor.insertText(m_logBuffer);
    m_logBuffer.clear();

    // Ограничение количества строк в логе
    QTextDocument *doc = m_logArea->document();
    while (doc->lineCount() > m_maxLogLines) {
        cursor.movePosition(QTextCursor::Start);
        cursor.select(QTextCursor::LineUnderCursor);
        cursor.removeSelectedText();
        cursor.deleteChar(); // Удаляем символ новой строки
    }

    m_logArea->verticalScrollBar()->setValue(m_logArea->verticalScrollBar()->maximum());

    // Обновляем подсветку только для новых строк
    if (auto highlighter = m_logArea->findChild<LogHighlighter*>()) {
        int lastBlock = doc->blockCount() - 1;
        int startBlock = qMax(0, lastBlock - 100); // Подсвечиваем последние 100 блоков
        for (int i = startBlock; i <= lastBlock; ++i) {
            highlighter->rehighlightBlock(doc->findBlockByNumber(i));
        }
    }
}

void MainWindow::applyFilter()
{
    qDebug() << "Entering applyFilter";
    QString filterText = m_filterInput->text().trimmed();
    int linesAfter = m_linesAfter->value();

    m_logArea->clear();

    if (filterText.isEmpty()) {
        m_logArea->setPlainText(m_fullLog);
        return;
    }

    QStringList lines = m_fullLog.split('\n', Qt::KeepEmptyParts);

    QRegularExpression regex(QRegularExpression::escape(filterText), QRegularExpression::CaseInsensitiveOption);
    QString filteredLog;
    bool foundMatch = false;
    int lineCount = 0;

    for (int i = 0; i < lines.size(); ++i) {
        if (lines[i].contains(regex) || lineCount > 0) {
            foundMatch = true;
            filteredLog += lines[i] + "\n";

            if (lines[i].contains(regex)) {
                lineCount = linesAfter;
            } else {
                lineCount--;
            }
        }
    }

    if (foundMatch) {
        m_logArea->setPlainText(filteredLog);
    } else {
        m_logArea->setPlainText("Ничего не найдено.");
    }

    m_logArea->verticalScrollBar()->setValue(0);
}

void MainWindow::changeFontSize(int size)
{
    QFont font = m_logArea->font();
    font.setPointSize(size);
    m_logArea->setFont(font);
}

void MainWindow::changeFont()
{
    bool ok;
    QFont font = QFontDialog::getFont(&ok, m_logArea->font(), this, "Выберите шрифт");
    if (ok) {
        m_logArea->setFont(font);
    }
}

void MainWindow::changeBackground()
{
    QColor color = QColorDialog::getColor(m_logArea->palette().color(QPalette::Base), this, "Выберите цвет фона");
    if (color.isValid()) {
        QPalette p = m_logArea->palette();
        p.setColor(QPalette::Base, color);
        m_logArea->setPalette(p);
    }
}

void MainWindow::changeForeground()
{
    QColor color = QColorDialog::getColor(m_logArea->palette().color(QPalette::Text), this, "Выберите цвет текста");
    if (color.isValid()) {
        QPalette p = m_logArea->palette();
        p.setColor(QPalette::Text, color);
        m_logArea->setPalette(p);
    }
}

void MainWindow::updateAvailableTags()
{
    qDebug() << "Entering updateAvailableTags";
    m_availableTags.clear();
    QString playbookPath = m_selectedPlaybook;
    if (playbookPath.isEmpty()) {
        return;
    }

    sftp_file file = sftp_open(m_sftpSession, playbookPath.toStdString().c_str(), O_RDONLY, 0);
    if (file == nullptr) {
        QMessageBox::critical(this, "Ошибка", "Не удалось открыть файл: " + QString(ssh_get_error(m_sshSession)));
        return;
    }

    QByteArray content;
    char buffer[4096];
    ssize_t nbytes;
    while ((nbytes = sftp_read(file, buffer, sizeof(buffer))) > 0) {
        content.append(buffer, nbytes);
    }

    if (nbytes < 0) {
        QMessageBox::critical(this, "Ошибка", "Ошибка при чтении файла: " + QString(ssh_get_error(m_sshSession)));
        sftp_close(file);
        return;
    }

    sftp_close(file);

    try {
        YAML::Node playbook = YAML::Load(content.toStdString());
        std::function<void(const YAML::Node&)> extractTags = [&](const YAML::Node& node) {
            if (node.IsMap()) {
                if (node["tags"]) {
                    if (node["tags"].IsSequence()) {
                        for (const auto& tag : node["tags"]) {
                            m_availableTags << QString::fromStdString(tag.as<std::string>());
                        }
                    } else if (node["tags"].IsScalar()) {
                        m_availableTags << QString::fromStdString(node["tags"].as<std::string>());
                    }
                }
                for (const auto& it : node) {
                    extractTags(it.second);
                }
            } else if (node.IsSequence()) {
                for (const auto& item : node) {
                    extractTags(item);
                }
            }
        };

        extractTags(playbook);
        m_availableTags.removeDuplicates();
    } catch (const YAML::Exception& e) {
        QMessageBox::warning(this, "Ошибка", QString("Не удалось прочитать теги из плейбука: %1").arg(e.what()));
    }
}

void MainWindow::selectTags()
{
    qDebug() << "Entering selectTags";
    updateAvailableTags();

    if (m_availableTags.isEmpty()) {
        QMessageBox::information(this, "Информация", "В выбранном плейбуке нет доступных тегов.");
        return;
    }

    QDialog dialog(this);
    dialog.setWindowTitle("Выбор тегов");
    QVBoxLayout* layout = new QVBoxLayout(&dialog);

    QListWidget* availableTagsList = new QListWidget(&dialog);
    availableTagsList->setSelectionMode(QAbstractItemView::MultiSelection);
    for (const QString& tag : m_availableTags) {
        QListWidgetItem* item = new QListWidgetItem(tag, availableTagsList);
        item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
        item->setCheckState(Qt::Unchecked);
    }
    layout->addWidget(new QLabel("Доступные теги:"));
    layout->addWidget(availableTagsList);

    QListWidget* selectedTagsList = new QListWidget(&dialog);
    selectedTagsList->setDragDropMode(QAbstractItemView::InternalMove);
    layout->addWidget(new QLabel("Выбранные теги (перетащите для изменения порядка):"));
    layout->addWidget(selectedTagsList);

    QPushButton* okButton = new QPushButton("OK", &dialog);
    connect(okButton, &QPushButton::clicked, &dialog, &QDialog::accept);
    layout->addWidget(okButton);

    connect(availableTagsList, &QListWidget::itemChanged, [selectedTagsList](QListWidgetItem* item) {
        if (item->checkState() == Qt::Checked) {
            selectedTagsList->addItem(item->text());
        } else {
            for (int i = 0; i < selectedTagsList->count(); ++i) {
                if (selectedTagsList->item(i)->text() == item->text()) {
                    delete selectedTagsList->takeItem(i);
                    break;
                }
            }
        }
    });

    // Предварительно выбираем теги, которые уже были выбраны
    QStringList currentTags = m_tags->text().split(",", Qt::SkipEmptyParts);
    for (const QString& tag : currentTags) {
        QList<QListWidgetItem*> items = availableTagsList->findItems(tag.trimmed(), Qt::MatchExactly);
        if (!items.isEmpty()) {
            items.first()->setCheckState(Qt::Checked);
        }
    }

    if (dialog.exec() == QDialog::Accepted) {
        QStringList selectedTags;
        for (int i = 0; i < selectedTagsList->count(); ++i) {
            selectedTags << selectedTagsList->item(i)->text();
        }
        m_tags->setText(selectedTags.join(","));
    }
}

bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
    if (event->type() == QEvent::KeyPress) {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
        if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) {
            if (obj == m_limit) {
                runPlaybook();
                return true;
            } else if (obj == m_filterInput) {
                applyFilter();
                return true;
            } else if (obj == m_serverPasswordInput) {
                connectToServer();
                return true;
            }
        }
    }
    return QObject::eventFilter(obj, event);
}

void MainWindow::performKinit()
{
    if (!m_sshSession || !ssh_is_connected(m_sshSession)) {
        QMessageBox::warning(this, "Предупреждение", "Нет подключения к серверу");
        return;
    }

    QString password = m_sudoPassword->text();
    if (password.isEmpty()) {
        QMessageBox::warning(this, "Предупреждение", "Введите пароль для Kinit");
        return;
    }

    QString command = "echo '" + password + "' | kinit ansible-admws@MAIN.FARMLEND.RU";

    ssh_channel channel = ssh_channel_new(m_sshSession);
    if (channel == nullptr) {
        appendToLog("Ошибка: Не удалось создать SSH канал\n");
        return;
    }

    if (ssh_channel_open_session(channel) != SSH_OK) {
        appendToLog("Ошибка: Не удалось открыть SSH сессию\n");
        ssh_channel_free(channel);
        return;
    }

    if (ssh_channel_request_exec(channel, command.toUtf8().constData()) != SSH_OK) {
        appendToLog("Ошибка: Не удалось выполнить команду Kinit\n");
        ssh_channel_close(channel);
        ssh_channel_free(channel);
        return;
    }

    char buffer[256];
    int nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);

    int exit_status = ssh_channel_get_exit_status(channel);
    ssh_channel_close(channel);
    ssh_channel_free(channel);

    if (exit_status == 0) {
        appendToLog("\nKinit выполнен успешно\n");
    } else {
        if (nbytes > 0) {
            QString output = QString::fromUtf8(buffer, nbytes);
            appendToLog("\nОшибка при выполнении Kinit: " + output);
        } else {
            appendToLog("\nОшибка при выполнении Kinit");
        }
    }
}

void MainWindow::onPlaybookSelected(const QModelIndex &index)
{
    QModelIndex sourceIndex = m_proxyModel->mapToSource(index);
    QStandardItem *item = m_playbookModel->itemFromIndex(sourceIndex);
    if (item) {
        QString fullPath = item->data(Qt::UserRole).toString();
        if (fullPath.endsWith(".yml") || fullPath.endsWith(".yaml")) {
            m_selectedPlaybook = fullPath;
            qDebug() << "Selected playbook:" << m_selectedPlaybook;
            // Очистка поля тегов при выборе нового плейбука
            m_tags->clear();
        }
    }
}

void MainWindow::onPlaybookDoubleClicked(const QModelIndex &index)
{
    // Update 6: Check if playbook is running
    if (m_isPlaybookRunning) {
        QMessageBox::warning(this, "Предупреждение", "Нельзя редактировать плейбук во время выполнения");
        return;
    }
    QModelIndex sourceIndex = m_proxyModel->mapToSource(index);
    QStandardItem *item = m_playbookModel->itemFromIndex(sourceIndex);
    if (item) {
        QString fullPath = item->data(Qt::UserRole).toString();
        if (fullPath.endsWith(".yml") || fullPath.endsWith(".yaml")) {
            m_selectedPlaybook = fullPath;
            qDebug() << "Double-clicked playbook:" << m_selectedPlaybook;
            editPlaybook();
            // Очистка поля тегов при двойном клике на плейбук
            m_tags->clear();
        }
    }
}

void MainWindow::saveSettings()
{
    qDebug() << "Сохранение настроек";
    QSettings settings(QCoreApplication::applicationDirPath() + "/settings.ini", QSettings::IniFormat);

    // Сохранение геометрии окна и состояния сплиттера
    settings.setValue("windowGeometry", saveGeometry());
    settings.setValue("splitterState", m_mainSplitter->saveState());

    // Сохранение пути к плейбукам по умолчанию
    settings.setValue("defaultPlaybookPath", m_defaultPlaybookPath);

    // Сохранение последнего использованного плейбука и настроек
    settings.setValue("lastPlaybook", m_selectedPlaybook);
    settings.setValue("lastLimit", m_limit->text());
    settings.setValue("lastTags", m_tags->text());
    settings.setValue("lastExtraVars", m_extraVars->text());

    // Сохранение настроек шрифта и цвета
    settings.setValue("logFont", m_logArea->font().toString());
    settings.setValue("logBackgroundColor", m_logArea->palette().color(QPalette::Base));
    settings.setValue("logTextColor", m_logArea->palette().color(QPalette::Text));

    // Сохранение настроек подключения к серверу
    settings.setValue("serverHost", m_serverHostInput->text());
    settings.setValue("serverPort", m_serverPortInput->value());
    settings.setValue("serverUsername", m_serverUsernameInput->text());

    // Сохранение других настроек
    settings.setValue("useSudo", m_useSudoEnabled);
    settings.setValue("verbose", m_verboseEnabled);
    settings.setValue("clearLogOnRun", m_clearLogOnRunEnabled);
    settings.setValue("checkMode", m_checkModeEnabled);

    // Сохранение размера окна редактирования плейбука
    settings.setValue("editDialogSize", m_editDialogSize);

    // Сохранение темы
    settings.setValue("currentTheme", static_cast<int>(m_currentTheme));

    qDebug() << "Настройки сохранены";
}

void MainWindow::filterPlaybooks(const QString &text)
{
    qDebug() << "Filtering playbooks with text:" << text;
    m_proxyModel->setFilterFixedString(text);
}

void MainWindow::filterPlaybookItem(QStandardItem *item, const QString &text)
{
    bool match = item->text().contains(text, Qt::CaseInsensitive);
    item->setData(!match, Qt::UserRole + 1); // Используем Qt::UserRole + 1 для хранения состояния видимости

    for (int i = 0; i < item->rowCount(); ++i) {
        QStandardItem *childItem = item->child(i);
        filterPlaybookItem(childItem, text);
        if (childItem->data(Qt::UserRole + 1).toBool()) {
            match = false;
        }
    }
    item->setData(!match, Qt::UserRole + 1);
}

void MainWindow::createThemeActions()
{
    QMenu *themeMenu = menuBar()->addMenu(tr("Тема"));

    lightThemeAction = new QAction(tr("Светлая"), this);
    connect(lightThemeAction, &QAction::triggered, this, &MainWindow::setLightTheme);
    themeMenu->addAction(lightThemeAction);

    darkThemeAction = new QAction(tr("Темная"), this);
    connect(darkThemeAction, &QAction::triggered, this, &MainWindow::setDarkTheme);
    themeMenu->addAction(darkThemeAction);

    blueThemeAction = new QAction(tr("Синяя"), this);
    connect(blueThemeAction, &QAction::triggered, this, &MainWindow::setBlueTheme);
    themeMenu->addAction(blueThemeAction);
}

void MainWindow::setLightTheme()
{
    UIManager::instance().setTheme(UIManager::Light);
    m_currentTheme = UIManager::Light;
    saveSettings();
}

void MainWindow::setDarkTheme()
{
    UIManager::instance().setTheme(UIManager::Dark);
    m_currentTheme = UIManager::Dark;
    saveSettings();
}

void MainWindow::setBlueTheme()
{
    UIManager::instance().setTheme(UIManager::Blue);
    m_currentTheme = UIManager::Blue;
    saveSettings();
}

void MainWindow::keyPressEvent(QKeyEvent *event)
{
    if (event->key() == Qt::Key_F11)
    {
        if (isMaximized())
        {
            showNormal();
        }
        else
        {
            showMaximized();
        }
    } else if (event->key() == Qt::Key_F5) {
        runPlaybook();
    } else if (event->key() == Qt::Key_F6) {
        stopPlaybook();
    } else if (event->key() == Qt::Key_F2) {
        editPlaybook();
    }
    QMainWindow::keyPressEvent(event);
}

void MainWindow::collapseConnectionParams()
{
    m_animation->setStartValue(m_serverConnectionWidgetHeight);
    m_animation->setEndValue(0);
    m_animation->start();
    m_toggleButton->setIcon(QIcon::fromTheme("go-down"));
}

void MainWindow::startLogThread()
{
    // Дополнительная инициализация для потока чтения лога, если необходимо
}

void MainWindow::stopLogThread()
{
    // Очистка ресурсов потока чтения лога, если необходимо
    m_logThread.quit();
    m_logThread.wait();
}

void MainWindow::updateUIState()
{
    bool enabled = !m_isPlaybookRunning;
    m_playbookTreeView->setEnabled(enabled);
    // m_editButton->setEnabled(enabled);
    if (enabled) {
        m_playbookTreeView->setPalette(QPalette());
    } else {
        QPalette pal = m_playbookTreeView->palette();
        //pal.setColor(QPalette::Base, Qt::lightGray);
        pal.setColor(QPalette::Text, Qt::darkGray);
        m_playbookTreeView->setPalette(pal);
    }
}

void MainWindow::showAbout()
{
    QMessageBox::about(this, tr("О программе"),
                       tr("<h3>Ansible Playbook Launcher</h3>"
                          "<p>Версия: 1.0.3</p>"
                          "<p>Автор: Гилязов Раиль</p>"
                          "<p>Это приложение позволяет запускать Ansible Playbook на удаленных серверах через SSH.</p>"));
}

void MainWindow::showContextMenu(const QPoint &pos)
{
    QModelIndex index = m_playbookTreeView->indexAt(pos);
    if (index.isValid()) {
        m_contextMenu->exec(m_playbookTreeView->viewport()->mapToGlobal(pos));
    }
}

void MainWindow::addNewPlaybook()
{
    if (!m_sshSession || !m_sftpSession) {
        QMessageBox::warning(this, "Предупреждение", "Нет подключения к серверу");
        return;
    }

    QString newPlaybookName = QInputDialog::getText(this, "Новый плейбук", "Введите имя нового плейбука:");
    if (newPlaybookName.isEmpty()) {
        return;
    }

    if (!newPlaybookName.endsWith(".yml") && !newPlaybookName.endsWith(".yaml")) {
        newPlaybookName += ".yml";
    }

    QString fullPath = m_defaultPlaybookPath + "/" + newPlaybookName;

    // Создаем пустой файл на сервере
    sftp_file file = sftp_open(m_sftpSession, fullPath.toStdString().c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    if (!file) {
        QMessageBox::critical(this, "Ошибка", "Не удалось создать новый плейбук: " + QString(ssh_get_error(m_sshSession)));
        return;
    }
    sftp_close(file);

    // Обновляем список плейбуков
    updatePlaybookList();

    // Выбираем новый плейбук в дереве
    selectPlaybookInTree(fullPath);

    // Открываем новый плейбук для редактирования
    editPlaybook();
}

void MainWindow::deletePlaybook()
{
    if (!m_sshSession || !m_sftpSession) {
        QMessageBox::warning(this, "Предупреждение", "Нет подключения к серверу");
        return;
    }

    if (m_selectedPlaybook.isEmpty()) {
        QMessageBox::warning(this, "Предупреждение", "Плейбук не выбран");
        return;
    }

    QMessageBox::StandardButton reply;
    reply = QMessageBox::question(this, "Удаление плейбука",
                                  "Вы уверены, что хотите удалить плейбук " + m_selectedPlaybook + "?",
                                  QMessageBox::Yes|QMessageBox::No);
    if (reply == QMessageBox::Yes) {
        int result = sftp_unlink(m_sftpSession, m_selectedPlaybook.toStdString().c_str());
        if (result != SSH_OK) {
            QMessageBox::critical(this, "Ошибка", "Не удалось удалить плейбук: " + QString(ssh_get_error(m_sshSession)));
            return;
        }

        // Обновляем список плейбуков
        updatePlaybookList();

        // Очищаем выбранный плейбук
        m_selectedPlaybook.clear();
    }
}

void MainWindow::selectExtraVars()
{
    QDialog dialog(this);
    dialog.setWindowTitle("Выбор переменных");
    QVBoxLayout* layout = new QVBoxLayout(&dialog);

    QTableWidget* varsTable = new QTableWidget(&dialog);
    varsTable->setColumnCount(2);
    varsTable->setHorizontalHeaderLabels({"Переменная", "Значение"});
    varsTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
    layout->addWidget(varsTable);

    // Заполняем таблицу текущими значениями
    QString currentVars = m_extraVars->text();
    QStringList pairs = currentVars.split(" ", Qt::SkipEmptyParts);
    varsTable->setRowCount(pairs.size() + 1);
    
    int row = 0;
    for (const QString& pair : pairs) {
        QStringList keyValue = pair.split("=");
        if (keyValue.size() == 2) {
            varsTable->setItem(row, 0, new QTableWidgetItem(keyValue[0]));
            varsTable->setItem(row, 1, new QTableWidgetItem(keyValue[1]));
            row++;
        }
    }

    // Кнопки управления
    QHBoxLayout* buttonLayout = new QHBoxLayout;
    QPushButton* addButton = new QPushButton(" Добавить строку ", &dialog);
    connect(addButton, &QPushButton::clicked, [varsTable]() {
        int row = varsTable->rowCount();
        varsTable->setRowCount(row + 1);
    });
    buttonLayout->addWidget(addButton);

    QPushButton* okButton = new QPushButton(" OK ", &dialog);
    connect(okButton, &QPushButton::clicked, &dialog, &QDialog::accept);
    buttonLayout->addWidget(okButton);

    layout->addLayout(buttonLayout);

    if (dialog.exec() == QDialog::Accepted) {
        QStringList vars;
        for (int i = 0; i < varsTable->rowCount(); ++i) {
            QTableWidgetItem* varItem = varsTable->item(i, 0);
            QTableWidgetItem* valItem = varsTable->item(i, 1);
            if (varItem && valItem && !varItem->text().isEmpty() && !valItem->text().isEmpty()) {
                vars << QString("%1=%2").arg(varItem->text(), valItem->text());
            }
        }
        m_extraVars->setText(vars.join(" "));
    }
}

void MainWindow::validateExtraVars(const QString& text)
{
    QPalette palette = m_extraVars->palette();
    bool isValid = true;
    
    QStringList pairs = text.split(" ", Qt::SkipEmptyParts);
    for (const QString& pair : pairs) {
        if (!pair.contains("=") || pair.count("=") > 1) {
            isValid = false;
            break;
        }
    }

    if (isValid) {
        palette.setColor(QPalette::Text, Qt::black);
        m_runButton->setEnabled(true);
    } else {
        palette.setColor(QPalette::Text, Qt::red);
        m_runButton->setEnabled(false);
    }
    
    m_extraVars->setPalette(palette);
}
