#include "sMQTTBroker.h"
#include "SD_MMC.h"
#include <time.h>
#include <vector>

void calculateBroadcastIp() {
    IPAddress localIp = WiFi.localIP();
    broadcastIp = IPAddress(localIp[0], localIp[1], localIp[2], 255);
}

void sendUdpBroadcast(const char* udpMessage = "SamovarichLogger") {
    if (!WiFi.isConnected()) return;
    calculateBroadcastIp();
    udp.beginPacket(broadcastIp, RegSetup.udpLPort);
    udp.write((const uint8_t*)udpMessage, strlen(udpMessage));
    udp.endPacket();
}

// Структура для хранения информации о сессии клиента
struct ClientSession {
    IPAddress clientIP;        // IP адрес клиента
    String sessionStartTime;   // Время начала сессии
    unsigned long lastActivity; // Время последней активности
    bool active;               // Флаг активности
    String lastStData;         // Последние ST данные для новой сессии
    int pointCount;            // Счетчик точек в текущей сессии
    int maxPoints;             // Максимальное количество точек для этой сессии
    
    ClientSession() : active(false), lastActivity(0), pointCount(0), maxPoints(0), lastStData("") {}
};

// Функция для экранирования спецсимволов в строке
String escapeJSONString(String input) {
    String output = "";
    for (unsigned int i = 0; i < input.length(); i++) {
        char c = input.charAt(i);
        switch (c) {
            case '"': output += "\\\""; break;
            case '\\': output += "\\\\"; break;
            case '\b': output += "\\b"; break;
            case '\f': output += "\\f"; break;
            case '\n': output += "\\n"; break;
            case '\r': output += "\\r"; break;
            case '\t': output += "\\t"; break;
            default:
                if (c < 0x20) {
                    char buf[7];
                    sprintf(buf, "\\u%04x", c);
                    output += buf;
                } else {
                    output += c;
                }
        }
    }
    return output;
}

// Класс-наследник для обработки событий брокера
class MyMQTTBroker : public sMQTTBroker {
private:
    std::vector<ClientSession> activeSessions;
    
    int findSessionByIP(IPAddress ip) {
        for (size_t i = 0; i < activeSessions.size(); i++) {
            if (activeSessions[i].clientIP == ip && activeSessions[i].active) {
                return i;
            }
        }
        return -1;
    }
    
    IPAddress getClientIPFromEvent(sMQTTEvent *event) {
        sMQTTClient *client = nullptr;
        switch(event->Type()) {
            case Public_sMQTTEventType: {
                sMQTTPublicClientEvent *pubEvent = (sMQTTPublicClientEvent*)event;
                client = pubEvent->Client();
                break;
            }
            case NewClient_sMQTTEventType: {
                sMQTTNewClientEvent *newEvent = (sMQTTNewClientEvent*)event;
                client = newEvent->Client();
                break;
            }
            default:
                break;
        }
        if (client) {
            return client->getClientIP();
        }
        return IPAddress(0,0,0,0);
    }
    
    String ensureIPDirectory(IPAddress ip) {
        String ipStr = ip.toString();
        ipStr.replace(".", "_");
        
        String dirPath = "/" + ipStr;
        
        if (!SD_MMC.exists(dirPath)) {
            SD_MMC.mkdir(dirPath);
            Serial.println("Created directory for IP: " + ipStr);
        }
        
        return dirPath;
    }
    
    String getCurrentTimeString() {
        String tS = GetFDate();
        tS.replace(":", "-");
        tS.replace(" ", "_");
        return tS;
    }
    
    String getJSONFilename(IPAddress clientIP, String sessionTime, String type) {
        String ipDir = ensureIPDirectory(clientIP);
        return ipDir + "/" + sessionTime + "_" + type + ".json";
    }
    
    // Закрытие текущей сессии и создание новой
    void closeAndCreateNewSession(IPAddress clientIP, String lastStData) {
        int idx = findSessionByIP(clientIP);
        if (idx >= 0) {
            // Закрываем текущую сессию
            activeSessions[idx].active = false;
            Serial.println("Session closed for IP: " + clientIP.toString() + 
                        ", points logged: " + String(activeSessions[idx].pointCount));
            
            // Создаем новую сессию
            String timeStr = getCurrentTimeString();
            ClientSession newSession;
            newSession.clientIP = clientIP;
            newSession.sessionStartTime = timeStr;
            newSession.lastActivity = millis();
            newSession.active = true;
            newSession.lastStData = lastStData;
            newSession.pointCount = 0;
            newSession.maxPoints = RegSetup.LogPeriod;
            activeSessions.push_back(newSession);
            
            // Экранируем payload для JSON
            String escapedStData = escapeJSONString(lastStData);
            
            // Логируем ST для новой сессии
            String stJson = "{\"type\":\"session\",\"time\":\"" + GetFDate() + 
                        "\",\"data\":\"" + escapedStData + "\"}";
            appendToJSON(clientIP, "st", stJson);
            
            Serial.println("=== NEW SESSION CREATED for IP: " + clientIP.toString() + 
                        ", max points: " + String(RegSetup.LogPeriod) + " ===");
        }
    }
    
    // Логирование - всегда APPEND, без закрывающей скобки
    void appendToJSON(IPAddress clientIP, String subtopic, String jsonLine) {
        if (!sdEnable) return;
        
        int sessionIdx = findSessionByIP(clientIP);
        if (sessionIdx < 0) return;
        
        ClientSession &session = activeSessions[sessionIdx];
        session.lastActivity = millis();
        
        String filename = getJSONFilename(clientIP, session.sessionStartTime, subtopic);
        
        // Проверяем существует ли файл и его размер
        File checkFile = SD_MMC.open(filename, FILE_READ);
        bool fileExists = (checkFile && checkFile.size() > 0);
        if (checkFile) checkFile.close();
        
        if (!fileExists) {  // Новый файл - пишем начало массива и первую точку
            File file = SD_MMC.open(filename, FILE_WRITE);
            if (!file) {
                Serial.println("Failed to create file: " + filename);
                return;
            }
            file.print("[\n");
            file.print(jsonLine);
            file.close();
            Serial.println("Created new JSON file: " + filename);
        } else { // Существующий файл - добавляем запятую и новую точку
            File file = SD_MMC.open(filename, FILE_APPEND);
            if (!file) {
                Serial.println("Failed to open file: " + filename);
                return;
            }
            file.print(",\n");
            file.print(jsonLine);
            file.close();
        }
    }
    
    bool parseTopic(String fullTopic, String &subtopic, String &index) {
        int firstSlash = fullTopic.indexOf('/');
        if (firstSlash <= 0) return false;
        String root = fullTopic.substring(0, firstSlash);
        if (root != "SMV") return false;
        int secondSlash = fullTopic.indexOf('/', firstSlash + 1);
        if (secondSlash <= firstSlash) return false;
        int thirdSlash = fullTopic.indexOf('/', secondSlash + 1);
        if (thirdSlash <= secondSlash) return false;
        subtopic = fullTopic.substring(secondSlash + 1, thirdSlash);
        index = fullTopic.substring(thirdSlash + 1);
        return (index == "3");
    }
    
    String csvToJSON(String payload) {
        String result = "  {\"Date\":\"" + GetFDate() + "\"";
        int commaIndex = 0;
        int startPos = 0;
        int fieldNum = 0;
        const char* fieldNames[] = {
            "Tp", "Tc", "Tw", "Tb", "Pa", "N", "Tt", "V", "Dw", "P", "Ab", "Ap", "Pb"
        };
        int fieldCount = sizeof(fieldNames) / sizeof(fieldNames[0]);
        
        // Пропускаем первое поле (дату)
        int firstComma = payload.indexOf(',');
        if (firstComma > 0) {
            startPos = firstComma + 1;
        }
        
        // Парсим остальные поля
        while ((commaIndex = payload.indexOf(',', startPos)) >= 0 && fieldNum < fieldCount) {
            String value = payload.substring(startPos, commaIndex);
            value.trim();
            result += ",\"" + String(fieldNames[fieldNum]) + "\":" + value;            
            startPos = commaIndex + 1;
            fieldNum++;
        }
        
        // Последнее поле
        if (fieldNum < fieldCount && startPos < payload.length()) {
            String value = payload.substring(startPos);
            value.trim();
            result += ",\"" + String(fieldNames[fieldNum]) + "\":" + value;
        }
        result += "}";
        return result;
    }

public:
    MyMQTTBroker() {}
    
    virtual bool onEvent(sMQTTEvent *event) override {
        switch(event->Type()) {
            case Public_sMQTTEventType: {
                sMQTTPublicClientEvent *pubEvent = (sMQTTPublicClientEvent*)event;
                String topic = String(pubEvent->Topic().c_str());
                String payload = String(pubEvent->Payload().c_str());
                IPAddress clientIP = getClientIPFromEvent(event);
                
                String subtopic, index;
                if (parseTopic(topic, subtopic, index)) {
                    if (subtopic == "st") {
                        // Сохраняем ST данные для возможного создания новой сессии
                        String stData = payload;
                        
                        // Удаляем старую сессию, если есть
                        int oldIdx = findSessionByIP(clientIP);
                        if (oldIdx >= 0) {
                            activeSessions.erase(activeSessions.begin() + oldIdx);
                            Serial.println("Removed old session for IP: " + clientIP.toString());
                        }
                        
                        // Создаем новую сессию
                        String timeStr = getCurrentTimeString();
                        ClientSession newSession;
                        newSession.clientIP = clientIP;
                        newSession.sessionStartTime = timeStr;
                        newSession.lastActivity = millis();
                        newSession.active = true;
                        newSession.lastStData = stData;
                        newSession.pointCount = 0;
                        newSession.maxPoints = RegSetup.LogPeriod;
                        activeSessions.push_back(newSession);
                        
                        // Экранируем payload для JSON
                        String escapedStData = escapeJSONString(stData);
                        
                        // Логируем ST
                        String stJson = "{\"type\":\"session\",\"time\":\"" + GetFDate() + 
                                    "\",\"data\":\"" + escapedStData + "\"}";
                        appendToJSON(clientIP, "st", stJson);
                        Serial.println("=== NEW SESSION for IP: " + clientIP.toString() + 
                                    ", max points: " + String(RegSetup.LogPeriod) + " ===");
                    } else if (subtopic == "msg") {
                        int idx = findSessionByIP(clientIP);
                        if (idx >= 0) {
                            String msgJson = "{\"type\":\"message\",\"time\":\"" + GetFDate() + 
                                           "\",\"message\":\"" + payload + "\"}";
                            appendToJSON(clientIP, "msg", msgJson);
                        }
                        
                    } else if (subtopic == "log") {
                        int idx = findSessionByIP(clientIP);
                        if (idx >= 0) {
                            ClientSession &session = activeSessions[idx];
                            
                            // Проверяем, не превышен ли лимит точек
                            if (session.pointCount >= session.maxPoints && session.maxPoints > 0) {
                                // Достигнут лимит - закрываем текущую сессию и создаем новую
                                Serial.println("Max points reached for IP: " + clientIP.toString() + 
                                              " (" + String(session.pointCount) + "/" + String(session.maxPoints) + ")");
                                closeAndCreateNewSession(clientIP, session.lastStData);
                                // Повторно ищем новую сессию
                                idx = findSessionByIP(clientIP);
                                if (idx < 0) return true;
                            }
                            
                            // Обновляем ссылку на сессию (могла измениться после closeAndCreateNewSession)
                            ClientSession &currentSession = activeSessions[idx];
                            
                            // Конвертируем CSV в JSON
                            String logJson = csvToJSON(payload);
                            
                            // Логируем точку
                            appendToJSON(clientIP, "log", logJson);
                            currentSession.pointCount++;
                            
                            // Отладочный вывод каждые 100 точек
                            if (currentSession.pointCount % 100 == 0) {
                                Serial.println("IP: " + clientIP.toString() + 
                                              " points: " + String(currentSession.pointCount) + 
                                              "/" + String(currentSession.maxPoints));
                            }
                        }
                    }
                }
                break;
            }
            
            case NewClient_sMQTTEventType: {
                sMQTTNewClientEvent *newClientEvent = (sMQTTNewClientEvent*)event;
                String clientId = String(newClientEvent->Client()->getClientId().c_str());
                IPAddress clientIP = getClientIPFromEvent(event);
                Serial.println("New client: " + clientId + " from " + clientIP.toString());
                break;
            }
            
            default:
                break;
        }
        return true;
    }
    
    void cleanupSessions() {
        unsigned long now = millis();
        
        for (auto it = activeSessions.begin(); it != activeSessions.end();) {
            if (it->active && (now - it->lastActivity) > RegSetup.Session_timeout*1000) {
                Serial.println("Session timeout for IP: " + it->clientIP.toString() + 
                              ", points logged: " + String(it->pointCount));
                it->active = false;
                it = activeSessions.erase(it);
            } else {
                ++it;
            }
        }
    }
    
    bool hasActiveSessions() {
        cleanupSessions();
        return !activeSessions.empty();
    }
    
    String getSessionsInfo() {
        String info = "Active sessions: " + String(activeSessions.size()) + "\n";
        for (size_t i = 0; i < activeSessions.size(); i++) {
            info += "  IP: " + activeSessions[i].clientIP.toString() + 
                    ", started: " + activeSessions[i].sessionStartTime +
                    ", points: " + String(activeSessions[i].pointCount) + 
                    "/" + String(activeSessions[i].maxPoints) + "\n";
        }
        return info;
    }
};

MyMQTTBroker internalBroker;

bool enableBrokerClients() {    
    return internalBroker.hasConnectedClients();
}

void InitMQTT() {
    if (internalBroker.init(RegSetup.MQTT_Port, true)) {
        Serial.println("=== Internal MQTT Broker STARTED ===");
        Serial.println("Clients connect to: " + WiFi.localIP().toString() + 
                      ":" + String(RegSetup.MQTT_Port));
        Serial.println("JSON logging with APPEND mode");
        Serial.println("Max points per session: " + String(RegSetup.LogPeriod));
        Serial.println("=====================================");
    }
}

void loopMQTT() {
    static unsigned long lastCleanup = 0;
    
    internalBroker.update();
    
    if (millis() - lastCleanup >= 30000) {
        internalBroker.cleanupSessions();
        lastCleanup = millis();
    }
    
    static uint32_t targetTime = millis();
    if (millis() - targetTime > udpInterval) {
        targetTime = millis();
        sendUdpBroadcast();
    }
}

String getMQTTInfo() {
    String info = "Mode: JSON Broker\n";
    info += "Port: " + String(RegSetup.MQTT_Port) + "\n";
    info += internalBroker.getSessionsInfo();
    info += "SD logging: " + String(sdEnable ? "Yes" : "No") + "\n";
    info += "Max points/session: " + String(RegSetup.LogPeriod);
    return info;
}

bool hasActiveMQTTSessions() {
    return internalBroker.hasActiveSessions();
}