const utils = require('./generalUtils.js');

const fs = require("fs");
const childProcess = require('child_process')
const FeedbackEmitter = require("events");
class MyEmitter extends FeedbackEmitter {}
const feedbackEmitter = new MyEmitter();
const path = require('path');
const urlJoin = require('url-join');

interface RequestObject {
    content?: string | null,
    type?: string | null,
    fullName?: string | null,
    mobileNumber?: string | null,
    email?: string | null,
    grade?: string | number | null,
    gradeName?: string | null,
    gradeType?: string | null,
    photo?: string | null,
    userId?: number | null,
    spaceId?: string | null,
    deviceId?: string | null,
    apt?: number | null,
    pollFormData?: object | null
}

interface MediaContentBlob {
    audio?: Blob | null,
    video?: Blob | null,
    photo?: Blob | null
}

interface User {
    email: string | null,
    password: string | null,
    accessToken: string | null,
    id: number | null
}

const requestObject: RequestObject = {
    content: null,
    type: null,
    fullName: null,
    mobileNumber: null,
    email: null,
    grade: null,
    gradeName: null,
    gradeType: null,
    photo: null,
    userId: null,
    apt: null,
    spaceId: null,
    deviceId: null,
    pollFormData: null
}

const mediaContentBlob: MediaContentBlob = {
    audio: null,
    video: null,
    photo: null
}

const user: User = {
    email: null,
    password: null,
    accessToken: null,
    id: null
}

let url: string;
const videoConverted = true;
let submitClicked = false;
let rotationValue: 0 | 90 | 180 | 270 = 0;

function handleTextFeedback(textValue: string) {
    if (!textValue) {
        feedbackEmitter.emit('error', 'Текстовое сообщение не может быть пустым');
    } else {
        if (requestObject.content !== textValue) {
            console.log(`Текстовое сообщение изменено: ${textValue}`);
            requestObject.content = textValue;
            requestObject.type = "text";
        }
    }
}

function handleFullName(textValue: string) {
    if (!textValue) {
        feedbackEmitter.emit('error', 'Имя отправителя не может быть пустым');
    } else {
        if (requestObject.fullName !== textValue) {
            requestObject.fullName = textValue;
            console.log(`Имя отправителя изменено: ${textValue}`);
        }
    }
}

function handleMobileNumber(textValue: string) {
    if (!textValue || !utils.isMobileNumberValid(textValue)) {
        feedbackEmitter.emit('error', 'Неверно задан номер телефона оправителя');
    } else {
        if (requestObject.mobileNumber !== textValue) {
            requestObject.mobileNumber = textValue;
            console.log(`Номер телефона отправителя изменён: ${textValue}`);
        }
    }
}

function handleEmail(textValue: string) {
    if (!textValue || !utils.isEmailValid(textValue)) {
        feedbackEmitter.emit('error', 'Неверно задан почтовый адрес оправителя');
    } else {
        if (requestObject.email !== textValue) {
            requestObject.email = textValue;
            console.log(`Почтовый адрес отправителя изменён: ${textValue}`);
        }     
    }
}

function handleGrade(gradeValue: string | number, maxGradeValue = 5, name: any = null, type: any = null) {
    const innerGradeValue = Number(gradeValue);
    if (!innerGradeValue || innerGradeValue < 1 || innerGradeValue > maxGradeValue) {
        feedbackEmitter.emit('error', `Неверно задана оценка. Оценка должна быть числом от 1 до ${maxGradeValue}. Задано: ${gradeValue}`);
        return;
    }

    const gradeInHundredSystem = Math.round(100 / (maxGradeValue - 1) * (innerGradeValue - 1));
    if (requestObject.grade !== gradeInHundredSystem) {
        requestObject.grade = gradeInHundredSystem;
        if (name) requestObject.gradeName = name.toString();
        if (type) requestObject.gradeType = type.toString();
        console.log(`Оценка работы приложения изменена: ${gradeInHundredSystem}` +
        `${name ? '\nНазвание оценки: ' + name : ''}` +
        `${type ? '\nТип оценки: ' + type : ''}`);
    }
}

function handleAudio(audioContentBlob: Blob, requestObject: RequestObject) {
    if (!audioContentBlob) {
        feedbackEmitter.emit("error", "No audio content");
    }
    mediaContentBlob.audio = audioContentBlob;
    const reader = new FileReader();
    reader.readAsDataURL(audioContentBlob);
    reader.onloadend = () => {
        const audioBase64 = reader.result;
        if (audioBase64 === null || typeof audioBase64 === 'string') {
            requestObject.content = audioBase64;
            requestObject.type = "audio";
        }
        feedbackEmitter.emit('event', 'audio-added');
    }
}

function handleVideo(videoContentBlob: Blob, requestObject: RequestObject) {
    if (!videoContentBlob) {
        feedbackEmitter.emit("error", "No video content");
    }
    mediaContentBlob.video = videoContentBlob;
    const reader = new FileReader();

    reader.readAsDataURL(videoContentBlob);
    reader.onloadend = () => {
        const videoBase64 = reader.result;
        //Получаем base64 и записываем в конетент на случай, если повернуть видео не получится
        if (videoBase64 === null || typeof videoBase64 === 'string') {
            requestObject.content = videoBase64;//?.replace('x-matroska', 'mkv');
            requestObject.type = "video";
            if (rotationValue !== 0) {
                reader.readAsArrayBuffer(videoContentBlob);
            } else {
                feedbackEmitter.emit('event', 'video-added');
            }
        }
        
        if (videoBase64 !== null && typeof videoBase64 !== 'string' && rotationValue !== 0) {
            const buffer = Buffer.from(videoBase64);
            rotateVideo(buffer, rotationValue);
        }
    }
}

async function rotateVideo(bufferVideo: Buffer, rotationValue: 90 | 180 | 270 ) {
    try {
        const transpose = {
            90: "transpose=1",
            180: "transpose=2,transpose=2",
            270: "transpose=0",
        }
        fs.writeFileSync('../video.webm', bufferVideo, 'base64', () => console.log('video saved!') );
        //если директория содержит предыдущий сконвертированный файл, удаляем
        const dirEntries = fs.readdirSync('../');
        if (dirEntries.includes('out.webm')) {
            fs.unlinkSync('../out.webm');
            dirEntries.splice(dirEntries.indexOf('out.webm'), 1);
        }

        //`ffmpeg -i ../video.webm -vf transpose=${rotation} -c:v libvpx ../out.webm` - vp8 codec
        const ffmpegCommand = `ffmpeg -i ../video.webm -vf ${transpose[rotationValue]} ../out.webm`;
        await childProcess.exec(ffmpegCommand, async (error: unknown, stdout: unknown, stderr: unknown) => {  
            if (error || (typeof stderr === 'string' && !stderr.includes('bitrate='))) {  
                feedbackEmitter.emit('error', `Не удалось повернуть видео. Ошибка: ${error ? error : stderr}`);
            } else {
                feedbackEmitter.emit('data', `Видео успешно повёрнуто на ${rotationValue} градусов`);
                const blob = await fetch(path.join(__dirname, '..', 'out.webm')).then(r => r.blob());
                mediaContentBlob.video = blob;
                const reader = new FileReader();
                reader.readAsDataURL(blob);
                reader.onloadend = () => {
                    const videoBase64 = reader.result;
                    if (videoBase64 === null || typeof videoBase64 === 'string') {
                        requestObject.content = videoBase64;
                        requestObject.type = "video";
                        feedbackEmitter.emit('event', 'video-rotated');
                    }
                    if (submitClicked) {
                        submitData();
                    }
                };
            }
        });         
    } finally {
        feedbackEmitter.emit('event', 'video-added');
    }
}

async function makePhoto(streamArg = null) {
    const stream = streamArg ? streamArg : await navigator.mediaDevices.getUserMedia({ video: true });
    const videoTrack = stream.getVideoTracks()[0];
    /** ImageCapture - экспериментальная штука, не поддерживается
     * браузерами: Firefox, Safari. Добавить проверку.
    */
    const imageCapture = new ImageCapture(videoTrack);
    const imageBlob = await imageCapture.takePhoto();
    mediaContentBlob.photo = imageBlob;
    const tracks = stream.getTracks();
    tracks.forEach((track) => track.stop());

    await photoToBase64(imageBlob);
}

function photoToBase64(blob: Blob) {
    const reader = new FileReader();
    reader.readAsDataURL(blob);
    reader.onloadend = () => {
        const photoBase64 = reader.result;
        if (photoBase64 === null || typeof photoBase64 === 'string') {
            requestObject.photo = photoBase64;
        }
        feedbackEmitter.emit('event', 'photo-added');
    }
}

function getAudioHandlers(stream: MediaStream) {
    const mediaRecorderAudio = new MediaRecorder(stream);

    mediaRecorderAudio.addEventListener("dataavailable", (event) => {
        handleAudio(event.data, requestObject);
    })

    return [() => {
            mediaRecorderAudio.start();
        },
        () => {
            mediaRecorderAudio.stop();
        },
        () => {
            const tracks = stream.getTracks();
            tracks.forEach((track) => {
                track.stop();
            });
        }
    ]
}

function getVideoHandlers(stream: MediaStream) {
    const mediaRecorderVideo = new MediaRecorder(stream);

    mediaRecorderVideo.addEventListener('dataavailable', (event) => {
        handleVideo(event.data, requestObject);
    })

    return [
        () => {
            mediaRecorderVideo.start();
        },
        () => {
            mediaRecorderVideo.stop();
        },
        () => {
            const tracks = stream.getTracks();
            tracks.forEach((track) => {
                track.stop();
            });
        }
    ]
}

function getMediaContent() {
    return mediaContentBlob;
}

function getRequestObject() {
    return Object.fromEntries(Object.keys(requestObject).map((key) => {
        const TKey = key as keyof RequestObject;
        const value = requestObject[TKey];
        if (typeof value === 'string') {
           return value.includes('base64') ? [key, true] : [key, value];
        }
        return [key, value];
    }));
}

function clearRequestObject() {
    Object.keys(requestObject).map((key) => {
        const TKey = key as keyof RequestObject;
        if (TKey !== 'spaceId' && TKey !== 'deviceId') requestObject[TKey] = null;
    })

    Object.keys(mediaContentBlob).map((key) => {
        const TKey = key as keyof MediaContentBlob;
        mediaContentBlob[TKey] = null;
    })

    console.log('Объект отзыва успешно очищен');
}

async function submitData() {

    //Проверяем чтобы в отзыве были хоть какие-то оценочные данные (контент, фото или баллы)
    if (!requestObject.content && !requestObject.photo && !requestObject.grade && requestObject.grade !== 0) {
        feedbackEmitter.emit('error', 'Отправка отзыва невозможна. Отзыв не содержит каких-либо оценочных данных (контент (текстовой, аудио или видео), фото или баллы)');
        return;
    }

    submitClicked = true;
    if (!videoConverted) {
        feedbackEmitter.emit('error', 'handling-video');
        return;
    }
    
    submitClicked = false;

    const loginResponce = await fetch(urlJoin(url, 'auth/login'), {
        method: "POST",
        mode: "cors",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({
            email: user.email,
            password: user.password
        })
    });
    
    if (loginResponce.ok) {
        const login = await loginResponce.json();
        user.accessToken = login.accessToken;
        user.id = login.id;
        requestObject.userId = login.id;

        const feedbackResponce = await fetch(urlJoin(url, 'feedback'), {
            method: "POST",
            mode: "cors",
            headers: {
                "Content-Type": "application/json",
                "Authorization": `Bearer ${user.accessToken}`
            },
            body: JSON.stringify(requestObject)
        });

        if (feedbackResponce.ok && feedbackResponce.statusText === 'Created') {
            requestObject.content = requestObject.content?.slice( 0, 97) + '...';
            const notNullObj = Object.fromEntries(Object.entries(requestObject).filter(([_, v]) => v != null))
            feedbackEmitter.emit('sended', `Отзыв успешно добавлен: ${JSON.stringify(notNullObj, null, '\t')}`);
            clearRequestObject();
        } else {
            feedbackEmitter.emit('error', `Не удалось добавить отзыв. Код шибки: ${feedbackResponce.status}. Описание ошибки: ${feedbackResponce.statusText}`);
        }
    } else {
        feedbackEmitter.emit('error', `Не удалось получить токен. Код шибки: ${loginResponce.status}. Описание ошибки: ${loginResponce.statusText}`);
    }
}

function initFeedbackModule(address: string, rotation: any, login = 'feedback@neuro-city.ru', password = 'vdnhvdnh', place = '', device = '') {
    if (!utils.isUrlValid(address)) {
        feedbackEmitter.emit("error", 'Неверно задан URL адрес сервера. Адрес должен иметь формат http(s)://server.domen или http(s)://XXX.XXX.XXX.XXX:YYYYY. Задан: ' + address);
        return;
    }
    
    rotation = Number(rotation);
    if (![0, 90, 180, 270].includes(Number(rotation))) {
        feedbackEmitter.emit("error", 'Указано недопустимое значение градуса поворота камеры. Допустимые значения: 0, 90, 180, 270. Задано: ' + rotation);
        return;
    }
    if (!utils.isEmailValid(login)) {
        feedbackEmitter.emit("error", 'Неверно задана учётная запись пользователя. Задано: ' + login);
        return;
    }
    if (place && device) {
        if (!/^(\w{24})$/.test(place) || !/^(\w{24})$/.test(device)) {
            feedbackEmitter.emit("error", `
            Неверно задан ID платформы или устройства. Отзыв не будет привязан к платформе. ID платформы и устройства задаются строкой
            длиной 24 символа состоящей из латинских букв и цифр.  Задано: ${place}`);
        } else {
            requestObject.spaceId = place;
            requestObject.deviceId = device;
        }
    } else {
        feedbackEmitter.emit("data", "Не задан ID платформы или устройства. Отзыв не будет привязан к платформе");
    }

    url = address;
    rotationValue = rotation;
    user.email = login;
    user.password = password;

    feedbackEmitter.emit('inited', `Инициализация пакета отправки отзывов прошла успешно. Данные инициализации: 
    Адрес сервера: ${url}
    Угол поворота камеры: ${rotationValue}
    Учётная запись пользователя для отправки отзывов: ${login}
    ${requestObject.spaceId && requestObject.deviceId ? `Платформа: ${place}
    Устройство: ${device}` : ''}`);
}


module.exports = { 
    handleTextFeedback,
    handleFullName,
    handleMobileNumber,
    handleEmail,
    getAudioHandlers,
    getVideoHandlers,
    makePhoto,
    getMediaContent,
    submitData,
    initFeedbackModule,
    handleGrade,
    clearRequestObject,
    getRequestObject,
    feedbackEmitter
};
