// Copyright (C) 2024-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE

#include "ConfigurationManager.hpp"

#include <QDir>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QRegularExpression>
#include <QUuid>

#include <coreplugin/icore.h>

#include "Logger.hpp"
#include "ProviderNameMigration.hpp"

namespace QodeAssist::Settings {

ConfigurationManager::ConfigurationManager(QObject *parent)
    : QObject(parent)
{}

ConfigurationManager &ConfigurationManager::instance()
{
    static ConfigurationManager instance;
    return instance;
}

QVector<AIConfiguration> ConfigurationManager::getPredefinedConfigurations(
    ConfigurationType type)
{
    QVector<AIConfiguration> presets;

    AIConfiguration claudeOpus;
    claudeOpus.id = "preset_claude_opus";
    claudeOpus.name = "Claude Opus 4.7";
    claudeOpus.provider = "Claude";
    claudeOpus.model = "claude-opus-4-7";
    claudeOpus.url = "https://api.anthropic.com";
    claudeOpus.customEndpoint = "";
    claudeOpus.templateName = "Claude";
    claudeOpus.type = type;
    claudeOpus.isPredefined = true;

    AIConfiguration claudeSonnet;
    claudeSonnet.id = "preset_claude_sonnet";
    claudeSonnet.name = "Claude Sonnet 4.6";
    claudeSonnet.provider = "Claude";
    claudeSonnet.model = "claude-sonnet-4-6";
    claudeSonnet.url = "https://api.anthropic.com";
    claudeSonnet.customEndpoint = "";
    claudeSonnet.templateName = "Claude";
    claudeSonnet.type = type;
    claudeSonnet.isPredefined = true;

    AIConfiguration claudeHaiku;
    claudeHaiku.id = "preset_claude_haiku";
    claudeHaiku.name = "Claude Haiku 4.5";
    claudeHaiku.provider = "Claude";
    claudeHaiku.model = "claude-haiku-4-5-20251001";
    claudeHaiku.url = "https://api.anthropic.com";
    claudeHaiku.customEndpoint = "";
    claudeHaiku.templateName = "Claude";
    claudeHaiku.type = type;
    claudeHaiku.isPredefined = true;

    AIConfiguration codestral;
    codestral.id = "preset_codestral";
    codestral.name = "Codestral";
    codestral.provider = "Codestral";
    codestral.model = "codestral-latest";
    codestral.url = "https://codestral.mistral.ai";
    codestral.customEndpoint = "";
    codestral.templateName = type == ConfigurationType::CodeCompletion ? "Mistral AI FIM" : "Mistral AI Chat";
    codestral.type = type;
    codestral.isPredefined = true;

    AIConfiguration mistral;
    mistral.id = "preset_mistral";
    mistral.name = "Mistral";
    mistral.provider = "Mistral AI";
    mistral.model = type == ConfigurationType::CodeCompletion ? "codestral-latest" : "mistral-large-latest";
    mistral.url = "https://api.mistral.ai";
    mistral.customEndpoint = "";
    mistral.templateName = type == ConfigurationType::CodeCompletion ? "Mistral AI FIM" : "Mistral AI Chat";
    mistral.type = type;
    mistral.isPredefined = true;

    AIConfiguration geminiFlash;
    geminiFlash.id = "preset_gemini_flash";
    geminiFlash.name = "Gemini 2.5 Flash";
    geminiFlash.provider = "Google AI";
    geminiFlash.model = "gemini-2.5-flash";
    geminiFlash.url = "https://generativelanguage.googleapis.com/v1beta";
    geminiFlash.customEndpoint = "";
    geminiFlash.templateName = "Google AI";
    geminiFlash.type = type;
    geminiFlash.isPredefined = true;

    AIConfiguration qwenPlus;
    qwenPlus.id = "preset_qwen_plus";
    qwenPlus.name = "Qwen3.6 Plus";
    qwenPlus.provider = "Qwen (OpenAI Response)";
    qwenPlus.model = "qwen3.6-plus";
    qwenPlus.url = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1";
    qwenPlus.customEndpoint = "";
    qwenPlus.templateName = "OpenAI Responses";
    qwenPlus.type = type;
    qwenPlus.isPredefined = true;

    AIConfiguration qwenMax;
    qwenMax.id = "preset_qwen_max";
    qwenMax.name = "Qwen3.7 Max";
    qwenMax.provider = "Qwen (OpenAI Response)";
    qwenMax.model = "qwen3.7-max";
    qwenMax.url = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1";
    qwenMax.customEndpoint = "";
    qwenMax.templateName = "OpenAI Responses";
    qwenMax.type = type;
    qwenMax.isPredefined = true;

    AIConfiguration deepSeekFlash;
    deepSeekFlash.id = "preset_deepseek_flash";
    deepSeekFlash.name = "DeepSeek V4 Flash";
    deepSeekFlash.provider = "DeepSeek";
    deepSeekFlash.model = "deepseek-v4-flash";
    deepSeekFlash.url = "https://api.deepseek.com";
    deepSeekFlash.customEndpoint = "";
    deepSeekFlash.templateName = "OpenAI Compatible";
    deepSeekFlash.type = type;
    deepSeekFlash.isPredefined = true;

    AIConfiguration deepSeekPro;
    deepSeekPro.id = "preset_deepseek_pro";
    deepSeekPro.name = "DeepSeek V4 Pro";
    deepSeekPro.provider = "DeepSeek";
    deepSeekPro.model = "deepseek-v4-pro";
    deepSeekPro.url = "https://api.deepseek.com";
    deepSeekPro.customEndpoint = "";
    deepSeekPro.templateName = "OpenAI Compatible";
    deepSeekPro.type = type;
    deepSeekPro.isPredefined = true;

    AIConfiguration gpt;
    gpt.id = "preset_gpt";
    gpt.name = "gpt-5.5";
    gpt.provider = "OpenAI (Responses API)";
    gpt.model = "gpt-5.5";
    gpt.url = "https://api.openai.com/v1";
    gpt.customEndpoint = "";
    gpt.templateName = "OpenAI Responses";
    gpt.type = type;
    gpt.isPredefined = true;

    presets.append(claudeSonnet);
    presets.append(claudeHaiku);
    presets.append(claudeOpus);
    presets.append(gpt);
    presets.append(codestral);
    presets.append(mistral);
    presets.append(geminiFlash);
    presets.append(qwenPlus);
    presets.append(qwenMax);
    presets.append(deepSeekFlash);
    presets.append(deepSeekPro);

    return presets;
}

QString ConfigurationManager::configurationTypeToString(ConfigurationType type) const
{
    switch (type) {
    case ConfigurationType::CodeCompletion:
        return "code_completion";
    case ConfigurationType::Chat:
        return "chat";
    case ConfigurationType::QuickRefactor:
        return "quick_refactor";
    }
    return "unknown";
}

QString ConfigurationManager::getConfigurationDirectory(ConfigurationType type) const
{
    QString path = QString("%1/qodeassist/configurations/%2")
                       .arg(Core::ICore::userResourcePath().toFSPathString(),
                            configurationTypeToString(type));
    return path;
}

bool ConfigurationManager::ensureDirectoryExists(ConfigurationType type) const
{
    QDir dir(getConfigurationDirectory(type));
    if (!dir.exists()) {
        return dir.mkpath(".");
    }
    return true;
}

bool ConfigurationManager::loadConfigurations(ConfigurationType type)
{
    QVector<AIConfiguration> *configs = nullptr;
    switch (type) {
    case ConfigurationType::CodeCompletion:
        configs = &m_ccConfigurations;
        break;
    case ConfigurationType::Chat:
        configs = &m_caConfigurations;
        break;
    case ConfigurationType::QuickRefactor:
        configs = &m_qrConfigurations;
        break;
    }

    if (!configs) {
        return false;
    }

    configs->clear();

    QVector<AIConfiguration> predefinedConfigs = getPredefinedConfigurations(type);
    configs->append(predefinedConfigs);

    if (!ensureDirectoryExists(type)) {
        LOG_MESSAGE("Failed to create configuration directory");
        return false;
    }

    QDir dir(getConfigurationDirectory(type));
    QStringList filters;
    filters << "*.json";
    QFileInfoList files = dir.entryInfoList(filters, QDir::Files);

    for (const QFileInfo &fileInfo : files) {
        QFile file(fileInfo.absoluteFilePath());
        if (!file.open(QIODevice::ReadOnly)) {
            LOG_MESSAGE(QString("Failed to open configuration file: %1").arg(fileInfo.fileName()));
            continue;
        }

        QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
        file.close();

        if (!doc.isObject()) {
            LOG_MESSAGE(QString("Invalid configuration file: %1").arg(fileInfo.fileName()));
            continue;
        }

        QJsonObject obj = doc.object();
        AIConfiguration config;
        config.id = obj["id"].toString();
        config.name = obj["name"].toString();
        config.provider = migrateProviderName(obj["provider"].toString());
        config.model = obj["model"].toString();
        config.templateName = obj["template"].toString();
        config.url = obj["url"].toString();
        config.customEndpoint = obj["customEndpoint"].toString();
        config.type = type;
        config.formatVersion = obj.value("formatVersion").toInt(1);

        config.isPredefined = false;

        if (config.id.isEmpty() || config.name.isEmpty()) {
            LOG_MESSAGE(QString("Invalid configuration data in file: %1").arg(fileInfo.fileName()));
            continue;
        }

        configs->append(config);
    }

    emit configurationsChanged(type);
    return true;
}

bool ConfigurationManager::saveConfiguration(const AIConfiguration &config)
{
    if (!ensureDirectoryExists(config.type)) {
        LOG_MESSAGE("Failed to create configuration directory");
        return false;
    }

    QJsonObject obj;
    obj["formatVersion"] = config.formatVersion;
    obj["id"] = config.id;
    obj["name"] = config.name;
    obj["provider"] = config.provider;
    obj["model"] = config.model;
    obj["template"] = config.templateName;
    obj["url"] = config.url;
    obj["customEndpoint"] = config.customEndpoint;

    QString sanitizedName = config.name;
    sanitizedName.replace(" ", "_");
    sanitizedName.replace(QRegularExpression("[^a-zA-Z0-9_-]"), "");

    QString fileName = QString("%1/%2_%3.json")
                           .arg(getConfigurationDirectory(config.type), sanitizedName, config.id);

    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly)) {
        LOG_MESSAGE(QString("Failed to create configuration file: %1").arg(fileName));
        return false;
    }

    QJsonDocument doc(obj);
    file.write(doc.toJson(QJsonDocument::Indented));
    file.close();

    loadConfigurations(config.type);
    return true;
}

bool ConfigurationManager::deleteConfiguration(const QString &id, ConfigurationType type)
{
    AIConfiguration config = getConfigurationById(id, type);
    if (config.isPredefined) {
        LOG_MESSAGE(QString("Cannot delete predefined configuration: %1").arg(id));
        return false;
    }

    QDir dir(getConfigurationDirectory(type));
    QStringList filters;
    filters << QString("*_%1.json").arg(id);
    QFileInfoList files = dir.entryInfoList(filters, QDir::Files);

    if (files.isEmpty()) {
        LOG_MESSAGE(QString("Configuration file not found for id: %1").arg(id));
        return false;
    }

    for (const QFileInfo &fileInfo : files) {
        QFile file(fileInfo.absoluteFilePath());
        if (!file.remove()) {
            LOG_MESSAGE(QString("Failed to delete configuration file: %1")
                            .arg(fileInfo.absoluteFilePath()));
            return false;
        }
    }

    loadConfigurations(type);
    return true;
}

QVector<AIConfiguration> ConfigurationManager::configurations(ConfigurationType type) const
{
    switch (type) {
    case ConfigurationType::CodeCompletion:
        return m_ccConfigurations;
    case ConfigurationType::Chat:
        return m_caConfigurations;
    case ConfigurationType::QuickRefactor:
        return m_qrConfigurations;
    }
    return {};
}

AIConfiguration ConfigurationManager::getConfigurationById(const QString &id,
                                                           ConfigurationType type) const
{
    const QVector<AIConfiguration> &configs = configurations(type);
    for (const AIConfiguration &config : configs) {
        if (config.id == id) {
            return config;
        }
    }
    return AIConfiguration();
}

} // namespace QodeAssist::Settings

