#define INTERNAL_MQTT_BROKER_PORT 1883
#define SESSION_TIMEOUT 180000 // 3 минуты в миллисекундах

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

// Структура для хранения информации о сессии клиента
struct ClientSession {
    IPAddress clientIP;        // IP адрес клиента
    String sessionStartTime;   // Время начала сессии
    unsigned long lastActivity; // Время последней активности
    bool active;               // Флаг активности
    
    ClientSession() : active(false), lastActivity(0) {}
};

// Класс-наследник для обработки событий брокера
class MyMQTTBroker : public sMQTTBroker {
private:
    // Вектор активных сессий
    std::vector<ClientSession> activeSessions;
    
    // Поиск сессии по IP адресу
    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;
    }
    
    // Получение IP адреса клиента из события
    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;
            }
            case Subscribe_sMQTTEventType:
            case UnSubscribe_sMQTTEventType: {
                sMQTTSubUnSubClientEvent *subEvent = (sMQTTSubUnSubClientEvent*)event;
                client = subEvent->Client();
                break;
            }
            default:
                break;
        }
        
        if (client) {
            return client->getClientIP();
        }
        
        return IPAddress(0,0,0,0);
    }
    
    // Создание папки для IP адреса
    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;
    }
    
    // Создание новой сессии для клиента
    void createNewSession(IPAddress clientIP) {
        // Проверяем, есть ли уже сессия для этого IP
        int existingIdx = findSessionByIP(clientIP);
        
        String timeStr = getCurrentTimeString();
        
        if (existingIdx >= 0) {
            // Обновляем существующую сессию
            activeSessions[existingIdx].sessionStartTime = timeStr;
            activeSessions[existingIdx].lastActivity = millis();
            activeSessions[existingIdx].active = true;
            
            Serial.println("=== SESSION UPDATED for IP: " + clientIP.toString() + " ===");
            Serial.println("New session time: " + activeSessions[existingIdx].sessionStartTime);
        } else {
            // Создаем новую сессию
            ClientSession newSession;
            newSession.clientIP = clientIP;
            newSession.sessionStartTime = timeStr;
            newSession.lastActivity = millis();
            newSession.active = true;
            
            activeSessions.push_back(newSession);
            
            Serial.println("=== NEW SESSION STARTED for IP: " + clientIP.toString() + " ===");
            Serial.println("Session time: " + newSession.sessionStartTime);
        }
        
        Serial.println("Active sessions: " + String(activeSessions.size()));
    }
    
    // Получение текущего времени в формате для имени файла
    String getCurrentTimeString() {
        String tS = NTP.getFormattedDate2();
        tS.replace(":", "-");
        tS.replace(" ", "_");
        // Обрезаем миллисекунды если есть
        int dotIndex = tS.indexOf('.');
        if (dotIndex > 0) {
            tS = tS.substring(0, dotIndex);
        }
        return tS;
    }
    
    // Получение имени файла для подтопика
    String getLogFilename(IPAddress clientIP, String sessionTime, String subtopic) {
        String ipDir = ensureIPDirectory(clientIP);
        return ipDir + "/" + sessionTime + "_" + subtopic + ".txt";
    }
    
    // Логирование сообщения
    void logMessage(IPAddress clientIP, String subtopic, String message) {
        if (!sdEnable) return;
        
        int sessionIdx = findSessionByIP(clientIP);
        if (sessionIdx < 0) return; // Сессия не активна
        
        ClientSession &session = activeSessions[sessionIdx];
        session.lastActivity = millis();
        
        String filename = getLogFilename(clientIP, session.sessionStartTime, subtopic);
        
        // Открываем файл для добавления
        File logFile = SD_MMC.open(filename, FILE_APPEND);
        if (logFile) {
            if (filename.endsWith("msg.txt")) logFile.println(NTP.getFormattedDate2() + " " + message);
            else logFile.println(message);
            logFile.close();
            Serial.println("Logged to: " + filename + " for IP: " + clientIP.toString());
        } else {
            Serial.println("Failed to open log file: " + filename);
        }
    }
    
    // Парсинг топика
    bool parseTopic(String fullTopic, String &subtopic, String &index) {
        // Ожидаемая структура: SMV/токен/подтопик/индекс
        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);
        
        // Проверяем, что индекс равен "3"
        return (index == "3");
    }

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());
                
                // Получаем IP клиента
                IPAddress clientIP = getClientIPFromEvent(event);
                
                Serial.println("MQTT Publish from " + clientIP.toString() + ": " + topic + " = " + (payload.length() > 50 ? payload.substring(0, 50) + "..." : payload));
                
                // Парсим топик
                String subtopic, index;
                if (parseTopic(topic, subtopic, index)) {
                    // Это топик с индексом 3
                    
                    if (subtopic == "st") {
                        // Получение st/3 создает НОВУЮ сессию для этого IP
                        createNewSession(clientIP);
                        logMessage(clientIP, "st", payload);
                        
                    } else if (subtopic == "msg" || subtopic == "log") {
                        // Логируем только если есть активная сессия для этого IP
                        if (findSessionByIP(clientIP) >= 0) {
                            logMessage(clientIP, subtopic, payload);
                        }
                    }
                }
                break;
            }
            
            case NewClient_sMQTTEventType: {
                sMQTTNewClientEvent *newClientEvent = (sMQTTNewClientEvent*)event;
                String clientId = String(newClientEvent->Client()->getClientId().c_str());
                IPAddress clientIP = getClientIPFromEvent(event);
                Serial.println("New MQTT client connected: " + clientId + " from IP: " + clientIP.toString());
                break;
            }
            
            case RemoveClient_sMQTTEventType: {
                IPAddress clientIP = getClientIPFromEvent(event);
                Serial.println("MQTT client disconnected from IP: " + clientIP.toString());
                break;
            }
            
            case Subscribe_sMQTTEventType: {
                sMQTTSubUnSubClientEvent *subEvent = (sMQTTSubUnSubClientEvent*)event;
                String topic = String(subEvent->Topic().c_str());
                IPAddress clientIP = getClientIPFromEvent(event);
                Serial.println("Client from IP " + clientIP.toString() + " subscribed to: " + topic);
                break;
            }
            
            case LostConnect_sMQTTEventType: {
                //Serial.println("WiFi connection lost!");
                break;
            }
            
            default:
                break;
        }
        return true;
    }
    
    // Переопределяем метод publish для работы с Arduino String
    void publish(String topic, String message, uint8_t qos = 0, bool retain = false) {
        std::string stdTopic = std::string(topic.c_str());
        std::string stdMessage = std::string(message.c_str());
        sMQTTBroker::publish(stdTopic, stdMessage, qos, retain);
    }
    
    // Очистка неактивных сессий
    void cleanupSessions() {
        unsigned long now = millis();
        bool removed = false;
        
        for (auto it = activeSessions.begin(); it != activeSessions.end();) {
            if (it->active && (now - it->lastActivity) > SESSION_TIMEOUT) {
                Serial.println("Session timeout for IP: " + it->clientIP.toString());
                it->active = false;
                it = activeSessions.erase(it);
                removed = true;
            } else {
                ++it;
            }
        }
        
        if (removed) {
            Serial.println("Active sessions remaining: " + String(activeSessions.size()));
        }
    }
    
    // Проверка наличия активных сессий (для индикации на дисплее)
    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++) {
            if (activeSessions[i].active) {
                info += "  IP: " + activeSessions[i].clientIP.toString() + 
                        ", started: " + activeSessions[i].sessionStartTime + "\n";
            }
        }
        return info;
    }
    
    // Принудительное завершение сессии по IP
    void endSession(IPAddress ip) {
        int idx = findSessionByIP(ip);
        if (idx >= 0) {
            activeSessions[idx].active = false;
            activeSessions.erase(activeSessions.begin() + idx);
            Serial.println("Session manually ended for IP: " + ip.toString());
        }
    }
};

MyMQTTBroker internalBroker;
bool enableBrokerClients() {    
    return internalBroker.hasConnectedClients();
}
void InitMQTT() {
    if (internalBroker.init(INTERNAL_MQTT_BROKER_PORT, true)) {
        Serial.println("=== Internal MQTT Broker STARTED ===");
        Serial.println("Clients can connect to: " + WiFi.localIP().toString() + ":" + String(INTERNAL_MQTT_BROKER_PORT));
        Serial.println("Multi-session logging enabled - separate session per client IP");
        Serial.println("Session timeout: 3 minutes");
        Serial.println("=====================================");
    } else {
        Serial.println("Failed to start MQTT Broker!");
    }
}

void publishStatus() {
    String status = "Broker running\n";
    status += internalBroker.getSessionsInfo();
    
    internalBroker.publish("status", status, 0, false);
}

void loopMQTT() {
    static unsigned long lastStatusUpdate = 0;
    static unsigned long lastCleanup = 0;
    
    internalBroker.update();
    
    // Очистка неактивных сессий каждые 30 секунд
    if (millis() - lastCleanup >= 30000) {
        internalBroker.cleanupSessions();
        lastCleanup = millis();
    }
    
    if (millis() - lastStatusUpdate >= 10000) {
        publishStatus();
        lastStatusUpdate = millis();
    }
}

String getMQTTInfo() {
    String info = "Mode: Internal Broker (sMQTTBroker)\n";
    info += "Port: " + String(INTERNAL_MQTT_BROKER_PORT) + "\n";
    info += internalBroker.getSessionsInfo();
    
    if (sdEnable) {
        info += "SD logging: Enabled\n";
        
        File root = SD_MMC.open("/");
        if (root) {
            File file = root.openNextFile();
            int logCount = 0;
            unsigned long totalSize = 0;
            
            while (file) {
                if (!file.isDirectory() && String(file.name()).endsWith(".txt")) {
                    logCount++;
                    totalSize += file.size();
                }
                file = root.openNextFile();
            }
            
            info += "Log files: " + String(logCount) + "\n";
            
            if (totalSize > 1024 * 1024) {
                info += "Total size: " + String(totalSize / (1024 * 1024)) + " MB";
            } else if (totalSize > 1024) {
                info += "Total size: " + String(totalSize / 1024) + " KB";
            } else {
                info += "Total size: " + String(totalSize) + " B";
            }
        }
    } else {
        info += "SD logging: Disabled";
    }
    
    return info;
}

// Функция для индикации на дисплее - есть ли активные сессии
bool hasActiveMQTTSessions() {
    return internalBroker.hasActiveSessions();
}