const SessionsEventEmitter = require('events');

class sessionsEmitterClass extends SessionsEventEmitter {}
const sessionsEmitter = new sessionsEmitterClass();

const onSession = (e: string | symbol, action: (data: any) => void) =>
    sessionsEmitter.on(e, (data: any) => action(data));

class Session implements Session {
    constructor({ startLang, startTheme }: sessionProps) {
        this.langs = startLang ? { [startLang]: 1 } : null;
        this.themes = startTheme
            ? [
                  {
                      name: startTheme,
                      queryCount: 1,
                      startTime: +new Date(),
                      time: 0,
                  },
              ]
            : null;
        currentTheme = startTheme ? startTheme : null;
        this.pathCount = 0;
        this.startTime = +new Date();
    }
}

let currentSession: Session | null = null;
let currentTheme: string | null = null;
let currentLang: string | null = null;
let videophoneStartTime: number | null = null;
let idleTime = 0;

/** Начинает новую сессию, устанавливая язык, тему и интервал простоя. */
const startSessionModule = ({
    startLang,
    startTheme,
    idleInterval,
}: sessionProps) => {
    if (currentSession !== null && currentSession.endTime === null) {
        sessionsEmitter.emit('info', 'Прошлая сессия не была завершена');
        return;
    }

    currentSession = new Session({
        startLang,
        startTheme,
    });

    idleTime = idleInterval ? idleInterval : 0;
    currentLang = startLang ? startLang : null;
    currentTheme = startTheme ? startTheme : null;
};

/** Завершает текущую сессию, рассчитывает статистику и возвращает ее. */
const endSessionModule = () => {
    if (currentSession === null) {
        sessionsEmitter.emit('info', 'Сессия не начата');
        return;
    } else {
        currentSession.endTime = +new Date();
        if (currentSession.themes) {
            const lastTheme = currentSession.themes.find(
                (theme) => theme.name === currentTheme
            );
            if (lastTheme) {
                lastTheme.time = Math.round(
                    (+new Date() - lastTheme.startTime) / 1000
                );
            }
        }

        const stats = getSessionStats();
        currentSession = null;

        return stats;
    }
};

/** Возвращает статистические данные текущей сессии в удобном для сервера формате */
const getSessionStats = () => {
    if (!currentSession) {
        sessionsEmitter.emit('info', 'Сессия не начата');
        return;
    }
    if (!currentSession.endTime) {
        sessionsEmitter.emit('info', 'Сессия не была завершена');
        return;
    }

    const statisticsInfo: sessionStatistics = {
        langs: convertLangs(),
        themes: convertThemes(),
        sessions: [
            {
                time: Math.round(
                    (currentSession.endTime - currentSession.startTime)  -
                        idleTime * 1000
                ),
                pathCount: currentSession.pathCount,
            },
        ],
        menu: convertMenu(),
        categories: convertCategories(),
        objects: convertObjects(),
        videophone: convertVideophone(),
        takemobile: currentSession.takemobile
            ? currentSession.takemobile
            : {
                  clients: [],
                  googlePlayCount: 0,
                  appStoreCount: 0,
                  takeMobileClick: 0,
              },
        events: convertEvents(),
    };

    return statisticsInfo;
};

/** Преобразует информацию о языках в формат, удобный для отправки на сервер, включая количество запросов и количество сессий. */
const convertLangs = () => {
    const statisticLangs: lang[] = [];
    if (currentSession?.langs) {
        const langs = currentSession.langs;
        Object.keys(langs).forEach((lang) => {
            statisticLangs.push({
                name: lang,
                queryCount: langs[lang],
                sessionCount: 1,
            });
        });
    }

    return statisticLangs;
};

/** Преобразует информацию о темах в формат, удобный для отправки на сервер, включая количество запросов и время, затраченное на каждую тему. */
const convertThemes = () => {
    let statisticThemes: theme[] = [];

    if (currentSession?.themes) {
        const themes = currentSession.themes;
        statisticThemes = themes.map((theme) => {
            return {
                name: theme.name,
                queryCount: theme.queryCount,
                sessionTime: theme.time,
            };
        });
    }

    return statisticThemes;
};

/** Преобразует информацию о действиях в меню в формат, удобный для отправки на сервер, включая количество кликов по каждому пункту меню. */
const convertMenu = () => {
    const statisticMenu: menuActions = [];
    if (currentSession?.menu) {
        const menu = currentSession.menu;
        Object.keys(menu).forEach((key) => {
            statisticMenu.push({
                name: key,
                clickCount: menu[key],
            });
        });
    }

    return statisticMenu;
};

/** Преобразует информацию о действиях в категориях в формат, удобный для отправки на сервер, включая количество кликов, голосовых и клавиатурных действий. */
const convertCategories = () => {
    const statisticCategories: categories = [];
    if (currentSession?.categories) {
        const categories = currentSession.categories;
        Object.keys(categories).forEach((category) => {
            statisticCategories.push({
                name: category,
                clickCount: categories[category].clickCount,
                voiceCount: categories[category].voiceCount,
                keyboardCount: categories[category].keyboardCount,
            });
        });
    }

    return statisticCategories;
};

/** Преобразует информацию о событиях в формат, удобный для отправки на сервер, включая количество кликов, голосовых и клавиатурных действий. */
const convertEvents = () => {
    const statisticEvents: events = [];
    if (currentSession?.events) {
        const events = currentSession.events;
        Object.keys(events).forEach((event) => {
            statisticEvents.push({
                name: event,
                clickCount: events[event].clickCount,
                voiceCount: events[event].voiceCount,
                keyboardCount: events[event].keyboardCount,
            });
        });
    }

    return statisticEvents;
};

/** Преобразует информацию об объектах в формат, удобный для отправки на сервер, включая количество кликов, голосовых и клавиатурных действий. */
const convertObjects = () => {
    const statisticObjects: objects = [];
    if (currentSession?.objects) {
        const objects = currentSession.objects;
        Object.keys(objects).forEach((key) => {
            statisticObjects.push({
                name: key,
                type: objects[key].type,
                clickCount: objects[key].clickCount,
                voiceCount: objects[key].voiceCount,
                keyboardCount: objects[key].keyboardCount,
            });
        });
    }

    return statisticObjects;
};

/** Преобразует информацию о видеозвонках в формат, удобный для отправки на сервер, включая среднее время и общее количество видеозвонков. */
const convertVideophone = () => {
    if (currentSession?.videophone) {
        const videophone = currentSession.videophone;
        const statisticVideophone: videophone = {
            time:
                videophone.times.reduce((a, e) => a + e, 0) /
                videophone.times.length /
                1000,
            count: videophone.count,
        };
        return statisticVideophone;
    }
    return {
        time: 0,
        count: 0,
    };
};

/** Обрабатывает смену темы внутри текущей сессии. */
const handleThemeChange = (newThemeName: string) => {
    if (currentSession === null) {
        sessionsEmitter.emit('info', 'Сессия не начата');
        return;
    }
    if (currentSession.themes === null || currentTheme === null) {
        sessionsEmitter.emit(
            'info',
            'Для активации функционала смены темы в начале сессии укажите стартовую тему.'
        );
        return;
    }
    if (newThemeName === currentTheme) {
        sessionsEmitter.emit('info', 'Нельзя сменить тему на такую же');
        return;
    }

    const newThemeEntry = currentSession.themes.find(
        (theme) => theme.name === newThemeName
    );
    const currentThemeEntry = currentSession.themes.find(
        (theme) => theme.name === currentTheme
    );

    if (currentThemeEntry) {
        currentThemeEntry.time += Math.round(
            (+new Date() - currentThemeEntry.startTime) / 1000
        );
        if (newThemeEntry) {
            newThemeEntry.startTime = +new Date();
            newThemeEntry.queryCount++;
        } else {
            currentSession.themes.push({
                name: newThemeName,
                startTime: +new Date(),
                queryCount: 1,
                time: 0,
            });
        }
        currentTheme = newThemeName;
    }
};

/** Обрабатывает смену языка внутри текущей сессии. */
const handleLangChange = (newLangName: string) => {
    if (currentSession === null) {
        sessionsEmitter.emit('info', 'Сессия не начата');
        return;
    }

    if (!currentSession.langs) {
        sessionsEmitter.emit(
            'info',
            'Для активации функционала смены языка в начале сессии укажите стартовый язык'
        );
        return;
    }

    if (newLangName === currentLang) {
        sessionsEmitter.emit('info', 'Нельзя сменить язык на такой же');
        return;
    }

    if (currentSession.langs[newLangName]) {
        currentSession.langs[newLangName]++;
    } else {
        currentSession.langs[newLangName] = 1;
    }

    currentLang = newLangName;
};

/** Обрабатывает запросы к путям внутри текущей сессии. */
const handlePathQuery = () => {
    if (!currentSession) {
        sessionsEmitter.emit('info', 'Сессия не начата');
        return;
    }

    currentSession.pathCount++;
};

/** Обрабатывает клики по пунктам меню внутри текущей сессии. */
const handleMenuClick = (menuName: string) => {
    if (!currentSession) {
        sessionsEmitter.emit('info', 'Сессия не начата');
        return;
    }

    if (!currentSession.menu) {
        currentSession.menu = {};
    }

    if (!currentSession.menu[menuName]) {
        currentSession.menu[menuName] = 0;
    }
    currentSession.menu[menuName]++;
};

/** Обрабатывает действия с категориями внутри текущей сессии. */
const handleCategoryAction = (
    categoryName: string,
    actionType: searchAction
) => {
    if (!currentSession) {
        sessionsEmitter.emit('info', 'Сессия не начата');
        return;
    }

    if (!currentSession.categories) {
        currentSession.categories = {};
    }

    if (!currentSession.categories[categoryName]) {
        currentSession.categories[categoryName] = {
            clickCount: 0,
            keyboardCount: 0,
            voiceCount: 0,
        };
    }

    currentSession.categories[categoryName][actionType]++;
};

/** Обрабатывает действия с событиями внутри текущей сессии. */
const handleEventAction = (eventName: string, actionType: searchAction) => {
    if (!currentSession) {
        sessionsEmitter.emit('info', 'Сессия не начата');
        return;
    }

    if (!currentSession.events) {
        currentSession.events = {};
    }

    if (!currentSession.events[eventName]) {
        currentSession.events[eventName] = {
            clickCount: 0,
            keyboardCount: 0,
            voiceCount: 0,
        };
    }

    currentSession.events[eventName][actionType]++;
};

/** Обрабатывает действия с приоритетными объектами внутри текущей сессии. */
const handlePriorityObjectAction = (
    objectName: string,
    actionType: searchAction,
    objectType?: string
) => {
    if (!currentSession) {
        sessionsEmitter.emit('info', 'Сессия не начата');
        return;
    }

    if (!currentSession.objects) {
        currentSession.objects = {};
    }

    if (!currentSession.objects[objectName]) {
        currentSession.objects[objectName] = {
            type: objectType ? objectType : 'none',
            clickCount: 0,
            voiceCount: 0,
            keyboardCount: 0,
        };
    }

    currentSession.objects[objectName][actionType]++;
};

/** Обрабатывает начало видеозвонка внутри текущей сессии. */
const handleVideophoneStart = () => {
    if (currentSession === null) {
        sessionsEmitter.emit('info', 'Сессия не начата');
        return;
    }

    if (videophoneStartTime) {
        sessionsEmitter.emit(
            'info',
            'Нельзя начать новый звонок, не завершив предыдущий'
        );
        return;
    }

    videophoneStartTime = +new Date();
};

/** Обрабатывает завершение видеозвонка внутри текущей сессии. */
const handleVideophoneEnd = () => {
    if (!currentSession) {
        sessionsEmitter.emit('info', 'Сессия не начата');
        return;
    }

    if (!videophoneStartTime) {
        sessionsEmitter.emit('info', 'Звонок не был начат, нечего завершать');
        return;
    }

    if (!currentSession.videophone) {
        currentSession.videophone = {
            count: 0,
            times: [],
        };
    }

    currentSession.videophone.count++;
    currentSession.videophone.times.push(+new Date() - videophoneStartTime);
};

/** Обрабатывает клики по функционалу "takemobile" внутри текущей сессии. */
const handleTakemobileClick = () => {
    if (!currentSession) {
        sessionsEmitter.emit('info', 'Сессия не начата');
        return;
    }

    if (!currentSession.takemobile) {
        currentSession.takemobile = {
            clients: [],
            googlePlayCount: 0,
            appStoreCount: 0,
            takeMobileClick: 0,
        };
    }

    currentSession.takemobile.takeMobileClick++;
};

/** Обрабатывает действия с "takemobile" внутри текущей сессии. */
const handleTakemobile = (clientNumber: string, storeType?: stores) => {
    if (!currentSession) {
        sessionsEmitter.emit('info', 'Сессия не начата');
        return;
    }

    if (!currentSession.takemobile) {
        currentSession.takemobile = {
            clients: [],
            googlePlayCount: 0,
            appStoreCount: 0,
            takeMobileClick: 1,
        };
    }

    if (storeType === 'googlePlay') {
        currentSession.takemobile.googlePlayCount++;
    }

    if (storeType === 'appStore') {
        currentSession.takemobile.appStoreCount++;
    }

    if (currentSession.takemobile.clients.find((el) => el === clientNumber)) {
        return;
    }

    currentSession.takemobile.clients.push(clientNumber);
};

module.exports = {
    onSession,
    startSessionModule,
    endSessionModule,
    handleThemeChange,
    handleLangChange,
    handleMenuClick,
    handlePathQuery,
    handleCategoryAction,
    handleEventAction,
    handlePriorityObjectAction,
    handleVideophoneStart,
    handleVideophoneEnd,
    handleTakemobileClick,
    handleTakemobile,
};
