#include "ANSLLM.h"
#include <CkGlobal.h>
#include <CkBinData.h>
#include <CkByteData.h>
#include <CkStringTable.h>
#include <CkStringBuilder.h>
#include <CkJsonObject.h>

static bool ansllmLicenceValid = false;
static std::once_flag ansllmLicenseOnceFlag;

namespace ANSCENTER
{
    static void VerifyGlobalANSLMLicense(const std::string& licenseKey) {
        try {
            static const std::vector<std::pair<int, std::string>> licenseChecks = {
                {1000, "ANNHUB-LV"},
                {1001, "DLHUB-LV"},
                {1002, "ODHUB-LV"},
                {1003, "ANSVIS"},
                {1004, "ANSFR"},
                {1005, "ANSOCR"},
                {1006, "ANSALPR"},
                {1007, "ANSCV"},
                {1008, "ANSSRT"}
            };
            ansllmLicenceValid = false;
            for (const auto& [productId, productName] : licenseChecks) {
                if (ANSCENTER::ANSLicenseHelper::LicenseVerification(licenseKey, productId, productName)) {
                    ansllmLicenceValid = true;
                    break; // Stop at the first valid license
                }
            }
        }
        catch (const std::exception& e) {
            ansllmLicenceValid = false;
        }
    }
    ANSLLM::ANSLLM() {
        _unlockCode = "ANSDRC.CB1122026_MEQCIFwO1IFQCG0BhZwsXFO68QUU6mDB5uge4duOsqOJanEyAiAB67ahqnXin4SRy0vIegISgbFlpldmbuS5gbU21GYVqA==";// "ANSDRC.CB1082025_Ax6P3M7F8B3d";//
    }
    ANSLLM::~ANSLLM() noexcept = default;

    void ANSLLM::CheckLicense() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            // Check once globally
            std::call_once(ansllmLicenseOnceFlag, [this]() {
                VerifyGlobalANSLMLicense(_licenseKey);
                });
            // Update this instance's local license flag
            _isLicenseValid = ansllmLicenceValid;
        }
        catch (const std::exception& e) {
            this->_logger.LogFatal("ANSLLM::CheckLicense. Error:", e.what(), __FILE__, __LINE__);
        }
    }
    void ANSLLM::CheckUnlockCode() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            CkGlobal glob;
            _unlockCode = "ANSDRC.CB1122026_MEQCIFwO1IFQCG0BhZwsXFO68QUU6mDB5uge4duOsqOJanEyAiAB67ahqnXin4SRy0vIegISgbFlpldmbuS5gbU21GYVqA==";// "ANSDRC.CB1082025_Ax6P3M7F8B3d";
            _isUnlockCodeValid = glob.UnlockBundle(_unlockCode.c_str());

            if (!_isUnlockCodeValid) {
                _logger.LogFatal("ANSLLM::CheckUnlockCode", glob.lastErrorText(), __FILE__, __LINE__);
                return;
            }

            int status = glob.get_UnlockStatus();
            if (status != 2) {
                _logger.LogDebug("ANSLLM::CheckUnlockCode", "Unlocked in trial mode.", __FILE__, __LINE__);
            }
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::CheckUnlockCode", e.what(), __FILE__, __LINE__);
        }
    }

    bool ANSLLM::Initialize(const std::string& licenseKey, bool localLLM) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            _licenseKey = licenseKey;
            _localLLM = localLLM;
            CheckLicense();
            CheckUnlockCode();

            if (_localLLM) {
                // Auto-configure for local Ollama: use custom provider with ChatCompletions API spec
                // Must use "custom" — setting "openai" resets BaseUrl to api.openai.com
                ai.put_Provider("custom");
                ai.put_ApiSpec("ChatCompletions");
                ai.put_BaseUrl(_ollamaBaseUrl.c_str());
                ai.put_ApiKey("ollama");  // Ollama doesn't require a real API key
                _logger.LogDebug("ANSLLM::Initialize", "Configured for local Ollama at " + _ollamaBaseUrl, __FILE__, __LINE__);
            }

            return _isLicenseValid && _isUnlockCodeValid;
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::Initialize", e.what(), __FILE__, __LINE__);
            return false;
        }
    }
    bool ANSLLM::SetLLMProvider(const std::string& llmProvider) {
        if (llmProvider.empty()) return false;
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            ai.put_Provider(llmProvider.c_str());
            return true;
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::SetLLMProvider", e.what(), __FILE__, __LINE__);
            return false;
        }
    }
    std::string ANSLLM::GetLLMProvider() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            CkString str;
            ai.get_Provider(str);
            const char* cstr = str.getString();
            return (cstr != nullptr) ? std::string(cstr) : std::string();
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::GetLLMProvider", e.what(), __FILE__, __LINE__);
            return "";
        }
    }
    bool ANSLLM::SetApiKey(const std::string& apiKey) {
        if (apiKey.empty()) return false;
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            ai.put_ApiKey(apiKey.c_str());
            return true;
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::SetApiKey", e.what(), __FILE__, __LINE__);
            return false;
        }

    }

    std::vector<std::string> ANSLLM::GetModelList() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        std::vector<std::string> modelList;

        try {
            CkStringTable st;

            if (!ai.GetModels(st)) {
                const char* errText = ai.lastErrorText();
                const std::string errorMsg = (errText != nullptr) ? errText : "Unknown error";
                _logger.LogError("ANSLLM::GetModelList",
                    "Failed to get models: " + errorMsg,
                    __FILE__, __LINE__);
                return modelList;
            }

            const int count = st.get_Count();

            if (count <= 0) {
                return modelList;
            }

            modelList.reserve(static_cast<size_t>(count));

            for (int i = 0; i < count; ++i) {
                const char* modelName = st.stringAt(i);
                if (modelName != nullptr && modelName[0] != '\0') {
                    modelList.emplace_back(modelName);
                }
            }

            return modelList;
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::GetModelList",
                std::string("Exception: ") + e.what(),
                __FILE__, __LINE__);
            return modelList;
        }
        catch (...) {
            _logger.LogFatal("ANSLLM::GetModelList",
                "Unknown exception occurred",
                __FILE__, __LINE__);
            return modelList;
        }
    }

    bool ANSLLM::SetModel(const std::string& modelName) {
        if (modelName.empty()) return false;
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            ai.put_Model(modelName.c_str());
            return true;
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::SetModel", e.what(), __FILE__, __LINE__);
            return false;
        }
    }
    bool ANSLLM::SetTranscriptMode(bool transcriptMode) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            ai.put_FullTranscript(transcriptMode);
            return true;
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::SetTranscriptMode", e.what(), __FILE__, __LINE__);
            return false;
        }
    }

    bool ANSLLM::InputAddText(const std::string& inputText) {
        if (inputText.empty()) return false;
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            ai.InputAddText(inputText.c_str());
            return true;
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::InputAddText", e.what(), __FILE__, __LINE__);
            return false;
        }
    }

    std::string ANSLLM::Ask(const std::string& prompt) {
        if (prompt.empty()) return "";
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            // Ask the AI for text output.
            bool success = ai.Ask(prompt.c_str());
            if (success == false) {
                const char* errText = ai.lastErrorText();
                return (errText != nullptr) ? std::string(errText) : std::string("Unknown error");
            }

            // Get the text response.
            CkStringBuilder sbResponse;
            ai.GetOutputTextSb(sbResponse);
            const char* cstr = sbResponse.getAsString();
            return (cstr != nullptr) ? std::string(cstr) : std::string();
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::Ask", e.what(), __FILE__, __LINE__);
            return "";
        }
    }
    bool ANSLLM::CreateConversation(const std::string& systemMessage, const std::string& developerMessage, const std::string& conversationName) {

        if (systemMessage.empty() || developerMessage.empty() || conversationName.empty()) {
            return false;
        }
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            return ai.NewConvo(conversationName.c_str(), systemMessage.c_str(), developerMessage.c_str());
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::CreateConversation", e.what(), __FILE__, __LINE__);
            return false;
        }
    }

    bool ANSLLM::SetConversation(const std::string& conversationName) {

        if (conversationName.empty()) return false;
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {

            ai.put_SelectedConvo(conversationName.c_str());
            return true;
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::SetConversation", e.what(), __FILE__, __LINE__);
            return false;
        }
    }

    bool ANSLLM::InputAddImageUrl(const std::string& imageURL, const std::string& summary) {
        if (imageURL.empty()) return false;
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            return ai.InputAddImageUrl(imageURL.c_str(), summary.c_str());
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::InputAddImageUrl", e.what(), __FILE__, __LINE__);
            return false;
        }
    }

    bool ANSLLM::InputAddImageFromPath(const std::string& filePath, const char* summary) {

        if (filePath.empty()) return false;
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            CkBinData bd;
            bool success = bd.LoadFile(filePath.c_str());
            if (!success) {
                const char* errText = bd.lastErrorText();
                _logger.LogError("ANSLLM::InputAddImageFromPath",
                    (errText != nullptr) ? errText : "Failed to load file",
                    __FILE__, __LINE__);
                return false;
            }
            return ai.InputAddImageData(bd, summary);
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::InputAddImageFromPath", e.what(), __FILE__, __LINE__);
            return false;
        }
    }
    bool ANSLLM::InputAddImageFromBase64(const std::string& jpegBase64String, const char* summary) {
        if (jpegBase64String.empty()) return false;

        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            CkBinData bd;

            // Load base64 encoded JPEG data into CkBinData
            bool success = bd.AppendEncoded(jpegBase64String.c_str(), "base64");
            if (!success) {
                _logger.LogError("ANSLLM::InputAddImage",
                    bd.lastErrorText(),
                    __FILE__, __LINE__);
                return false;
            }

            return ai.InputAddImageData(bd, summary);
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::InputAddImage", e.what(), __FILE__, __LINE__);
            return false;
        }
    }

    bool ANSLLM::InputAddFileData(const std::string& filePath, const char* summary) {
        if (filePath.empty()) return false;

        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            CkBinData bd;
            bool success = bd.LoadFile(filePath.c_str());
            if (!success) {
                const char* errText = bd.lastErrorText();
                _logger.LogError("ANSLLM::InputAddFileData",
                    (errText != nullptr) ? errText : "Failed to load file",
                    __FILE__, __LINE__);
                return false;
            }
            return ai.InputAddImageData(bd, summary);
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::InputAddFileData", e.what(), __FILE__, __LINE__);
            return false;
        }
    }
    bool ANSLLM::InputAddFileURL(const std::string& fileURL, const char* summary) {
        if (fileURL.empty()) return false;

        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {

            return ai.InputAddFileUrl(fileURL.c_str(), summary);
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::InputAddFileURL", e.what(), __FILE__, __LINE__);
            return false;
        }
    }

	// Extra methods
    bool ANSLLM::ClearInputs() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            ai.InputClear();
            return true;
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::ClearInputs", e.what(), __FILE__, __LINE__);
            return false;
        }
    }
    std::vector<std::string> ANSLLM::GetConversationList() {

        std::lock_guard<std::recursive_mutex> lock(_mutex);
		std::vector<std::string> convList;
        try {
            CkStringTable st;

            if (!ai.ListConvos(st)) {
                const char* errText = ai.lastErrorText();
                const std::string errorMsg = (errText != nullptr) ? errText : "Unknown error";
                _logger.LogError("ANSLLM::GetConversationList",
                    "Failed to get conversations: " + errorMsg,
                    __FILE__, __LINE__);
                return convList;
            }

            const int count = st.get_Count();

            if (count <= 0) {
                return convList;
            }

            convList.reserve(static_cast<size_t>(count));

            for (int i = 0; i < count; ++i) {
                const char* convName = st.stringAt(i);
                if (convName != nullptr && convName[0] != '\0') {
                    convList.emplace_back(convName);
                }
            }
            return convList;
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::GetConversationList", e.what(), __FILE__, __LINE__);
            return {};
        }
    }

    bool ANSLLM::DeleteConversation(const std::string& conversationName) {

        if (conversationName.empty()) return false;

        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            return ai.DeleteConvo(conversationName.c_str());
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::DeleteConversation", e.what(), __FILE__, __LINE__);
            return false;
        }
    }
    bool ANSLLM::SetUTF8(bool b) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            ai.put_Utf8(b);
            return true;
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::SetUTF8", e.what(), __FILE__, __LINE__);
            return false;
		}
    }

    bool ANSLLM::GetUTF8() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            return ai.get_Utf8();
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::GetUTF8", e.what(), __FILE__, __LINE__);
            return false;
        }
    }

    int ANSLLM::PollAi(bool abort) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            return ai.PollAi(abort);
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::PollAi", e.what(), __FILE__, __LINE__);
            return -1;
		}
    }
    bool ANSLLM::SetLocalLLM(bool localLLM) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            _localLLM = localLLM;
            if (_localLLM) {
                ai.put_Provider("custom");
                ai.put_ApiSpec("ChatCompletions");
                ai.put_BaseUrl(_ollamaBaseUrl.c_str());
                ai.put_ApiKey("ollama");
                _logger.LogDebug("ANSLLM::SetLocalLLM", "Configured for local Ollama at " + _ollamaBaseUrl, __FILE__, __LINE__);
            }
            return true;
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::SetLocalLLM", e.what(), __FILE__, __LINE__);
            return false;
        }
    }
    bool ANSLLM::SetOllamaBaseUrl(const std::string& baseUrl) {
        if (baseUrl.empty()) return false;
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            _ollamaBaseUrl = baseUrl;
            if (_localLLM) {
                ai.put_BaseUrl(_ollamaBaseUrl.c_str());
            }
            return true;
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::SetOllamaBaseUrl", e.what(), __FILE__, __LINE__);
            return false;
        }
    }
    bool ANSLLM::SetBaseURL(const std::string& baseURL) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            ai.put_BaseUrl(baseURL.c_str());
            return true;
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::SetBaseURL", e.what(), __FILE__, __LINE__);
            return false;
        }

    }
    int  ANSLLM::GetHeartbeatMs() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            return ai.get_HeartbeatMs();
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::GetHeartbeatMs", e.what(), __FILE__, __LINE__);
            return -1;
		}
    }
    bool ANSLLM::SetHeartbeatMs(int heartbeatMs) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            ai.put_HeartbeatMs(heartbeatMs);
            return true;
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::SetHeartbeatMs", e.what(), __FILE__, __LINE__);
            return false;
        }
	}
    bool ANSLLM::GetHostedConversation() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            return ai.get_HostedConvo();
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::GetHostedConversation", e.what(), __FILE__, __LINE__);
            return false;
        }

    }
    bool ANSLLM::SetHostedConversation(bool hostedConversation) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            ai.put_HostedConvo(hostedConversation);
            return true;
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::SetHostedConversation", e.what(), __FILE__, __LINE__);
            return false;
        }
	}

    bool ANSLLM::GetStreamingMode() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            return ai.get_Streaming();
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::GetStreamingMode", e.what(), __FILE__, __LINE__);
            return false;
		}

    }
    bool ANSLLM::SetStreamingMode(bool streamingMode) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            ai.put_Streaming(streamingMode);
            return true;
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::SetStreamingMode", e.what(), __FILE__, __LINE__);
            return false;
        }
    }

    int ANSLLM::GetIdleTimeoutMs() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            return ai.get_IdleTimeoutMs();
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::GetIdleTimeoutMs", e.what(), __FILE__, __LINE__);
            return -1;
        }
    }
    bool ANSLLM::SetIdleTimeoutMs(int idleTimeoutMs) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            ai.put_IdleTimeoutMs(idleTimeoutMs);
            return true;
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::SetIdleTimeoutMs", e.what(), __FILE__, __LINE__);
            return false;
        }
	}

    std::string ANSLLM::GetLastErrorMessage() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            const char* errorText = ai.lastErrorText();
            return (errorText != nullptr) ? std::string(errorText) : std::string();
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::GetLastErrorMessage", e.what(), __FILE__, __LINE__);
            return e.what();
        }
    }
    std::string ANSLLM::GetOutputText() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            CkString str;
            ai.GetOutputText(str);
            const char* cstr = str.getString();
            return (cstr != nullptr) ? std::string(cstr) : std::string();
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::GetOutputText", e.what(), __FILE__, __LINE__);
            return "";
		}
    }
    std::string ANSLLM::GetOutputTextSb() {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            CkStringBuilder sb;
            ai.GetOutputTextSb(sb);
            const char* cstr = sb.getAsString();
            return (cstr != nullptr) ? std::string(cstr) : std::string();
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::GetOutputTextSb", e.what(), __FILE__, __LINE__);
            return "";
        }
    }

    bool ANSLLM::SetSleepMs(int sleepMs) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            ai.SleepMs(sleepMs);
            return true;
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::SetSleepMs", e.what(), __FILE__, __LINE__);
            return false;
		}
    }

    bool ANSLLM::SetAskParams(double temperature) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        try {
            CkJsonObject obj;
            std::string strTemp = std::to_string(temperature);
			obj.UpdateString("temperature", strTemp.c_str());
			ai.SetAskParams(obj);
            return true;
        }
        catch (const std::exception& e) {
            _logger.LogFatal("ANSLLM::SetAskParams", e.what(), __FILE__, __LINE__);
            return false;
        }
    }
}
