import {
    MatrixClient,
    RustSdkCryptoStorageProvider,
    SimpleFsStorageProvider,
} from "matrix-bot-sdk";

import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs";

import {
    convertMsgType,
    logError,
    logInfo,
    readConfig,
    uploadMediaFromEvent,
    uploadMediaAsMessage,
    findCity,
    insertMessageIntoDB
} from './utils.js';

import fs from "fs";

import { I18n } from "i18n-js";

import { User } from "./user.js";

const config = readConfig();

const crypto = new RustSdkCryptoStorageProvider("./bot_data/encryption_bot_sled", StoreType.Sled);
const storage = new SimpleFsStorageProvider("./bot_data/bot.json");

const client = new MatrixClient(config.homeserverURL, config.token, storage, crypto);

client.on("room.message", async (roomId, event) => {
    if (event.sender === await client.getUserId()) return;
    if (event.content?.msgtype !== 'm.text' && event.content?.msgtype !== 'm.image' && event.content?.msgtype !== 'm.video') return;

    const i18n = new I18n({
        en: JSON.parse(fs.readFileSync("./translations/en.json")),
        ru: JSON.parse(fs.readFileSync("./translations/ru.json"))
    });


    let user = new User(roomId);

    if (!await user.exists()) {
        i18n.locale = 'en'
        await client.sendText(roomId, i18n.t(["errors", "usernotexists"]));
        i18n.locale = 'ru'
        await client.sendText(roomId, i18n.t(["errors", "usernotexists"]));
        let mxid = event.sender;

        await user.init(mxid);
        return;
    }

    const current_action = await user.getCurrentAction();

    const language = await user.getLanguage();
    i18n.locale = language;

    let answer = event.content.body;
    let msgtype = event.content.msgtype


    // // Updating actions are "<action>-u"
    let action = current_action.split("-")

    switch (action[0]) {
        case "wait_start":
            let command = answer.split(" ");
            if (command[0] !== "!start") return;
            if (command[1] !== "ru" && command[1] !== "en") return;

            await user.set("language", command[1]);
            await user.setState("location");
            await client.sendText(roomId, i18n.t(["setup", "location"]));
            break;
        case "location":
            let number = parseInt(answer);
            if (!number) {
                const cities = await findCity(answer);
                await client.sendText(roomId, i18n.t(["setup", "choosecity"], {
                    number1: cities[0].id,
                    name1: cities[0].name,
                    country1: cities[0].country,
                    lat1: cities[0].lat,
                    lng1: cities[0].lng,

                    number2: cities[1].id,
                    name2: cities[1].name,
                    country2: cities[1].country,
                    lat2: cities[1].lat,
                    lng2: cities[1].lng,

                    number3: cities[2].id,
                    name3: cities[2].name,
                    country3: cities[2].country,
                    lat3: cities[2].lat,
                    lng3: cities[2].lng,

                    number4: cities[3].id,
                    name4: cities[3].name,
                    country4: cities[3].country,
                    lat4: cities[3].lat,
                    lng4: cities[3].lng,

                    number5: cities[4].id,
                    name5: cities[4].name,
                    country5: cities[4].country,
                    lat5: cities[4].lat,
                    lng5: cities[4].lng,
                }));
                return;
            } else {
                if (action[1] == "u")
                    await user.update(answer)
                else
                    await user.process(answer)
            }
            break;
        case "range":
            const range = parseInt(answer);
            if (!range && range !== 0)
                await client.sendText(roomId, i18n.t(["setup", "range"]));
            else
                if (action[1] == "u")
                    await user.update(range)
                else
                    await user.process(range);
            break;
        case "name":
            if (action[1] == "u")
                await user.update(answer)
            else
                await user.process(answer);
            break;
        case "age":
            const age = parseInt(answer);
            if (!age || age > 100)
                await client.sendText(roomId, i18n.t(["setup", "age"]));
            else if (age < 14) {
                await client.sendText(roomId, i18n.t(["errors", "tooyoung"]));
                await client.leaveRoom(roomId);
                await user.fullErase();
                return;
            } else
                if (action[1] == "u")
                    await user.update(age)
                else
                    await user.process(age);
            break;
        case "sex":
            const sex = answer.toLowerCase().trim();
            if (sex !== "female" &&
                sex !== "f" &&
                sex !== "male" &&
                sex !== "m")
                await client.sendText(roomId, i18n.t(["setup", "sex"]));
            else
                if (action[1] == "u")
                    await user.update(sex[0])
                else
                    await user.process(sex[0]);
            break;
        case "interest":
            const interest = answer.toLowerCase().trim();
            if (interest !== "female" &&
                interest !== "f" &&
                interest !== "male" &&
                interest !== "m" &&
                interest !== "b" &&
                interest !== "both")
                await client.sendText(roomId, i18n.t(["setup", "interest"]));
            else
                if (action[1] == "u")
                    await user.update(interest[0])
                else
                    await user.process(interest[0]);
            break;
        case "description":
            if (action[1] == "u")
                await user.update(answer)
            else
                await user.process(answer);
            break;
        case "language":
            if (answer !== "en" && answer !== "ru") {
                await client.sendText(roomId, i18n.t(["setup", "language"]));
                return;
            }
            i18n.locale = answer
            user.i18n.locale = answer
            if (action[1] == "u")
                await user.update(answer);
            else
                await user.process(answer);
            await user.getLanguage()
            break;
        case "pictures":
            if (event.content?.msgtype !== 'm.image' && event.content?.msgtype !== 'm.video') {
                await client.sendText(roomId, i18n.t(["setup", "done"]));
                await user.setState("view_profiles")
                await user.showRandomProfile();
            } else {

                const pictures_count = (await user.getProfilePictures()).length

                if (pictures_count >= config.maxAmountOfPhotoesPerUser) {

                    await client.sendText(roomId, i18n.t(["errors", "toomuch"]));
                    await user.setState("view_profiles");
                    await user.showRandomProfile();
                } else {

                    let mxc = await uploadMediaFromEvent(client, event);
                    let type = convertMsgType(msgtype);
                    await user.addProfilePicture(type, mxc);
                    const pictures_count = (await user.getProfilePictures()).length;
                    if (pictures_count < config.maxAmountOfPhotoesPerUser) {
                        await client.sendText(roomId, i18n.t(["setup", "more"], { amount: String(config.maxAmountOfPhotoesPerUser - pictures_count) }));
                    } else {
                        await client.sendText(roomId, i18n.t(["setup", "enough"]));
                        await client.sendText(roomId, i18n.t(["setup", "done"]));
                        await user.setState("view_profiles");
                        await user.showRandomProfile();
                    }
                }
            }
            break;
        case "view_profiles":
            if (answer == '👍️' || answer == '❤️' || answer == '1') {
                let currently_viewing = await user.getCurrentlyViewingProfile();

                await user.addLikeTo(currently_viewing);
                let value = await user.checkForMutualLikeWith(currently_viewing);
                if (value) {
                    await client.sendText(currently_viewing, i18n.t(["general", "newlikes"]));
                    await client.sendText(roomId, i18n.t(["general", "newlikes"]));
                }
            } else if (answer == '💌' || answer == '3') {
                await user.setState('send_message');
                await client.sendText(roomId, i18n.t(["general", "message"]));
                return;
            } else if (answer == '🏠️' || answer == '4') {
                await client.sendText(roomId, i18n.t(["general", "menu"]));
                await user.setState('menu');
                return;
            }
            await user.showRandomProfile();
            break;
        case "send_message":
            let recipient = await user.getCurrentlyViewingProfile();
            let content;

            if (msgtype == "m.image" || msgtype == "m.video") {
                let mxc = await uploadMediaFromEvent(client, event);
                await uploadMediaAsMessage(roomId, msgtype, mxc);
                content = mxc;
            } else {
                content = answer;
            }

            if (await insertMessageIntoDB(roomId, recipient, msgtype, content)) {
                await client.sendText(recipient, i18n.t(["general", "newmessage"]));
                await client.sendText(roomId, i18n.t(["general", "messagesent"]));
            } else {
                await client.sendText(roomId, i18n.t(["errors", "alreadymessaged"]));
            }
            await user.setState("view_profiles");
            await user.showRandomProfile();
            break;
        case 'menu':
            switch (answer) {
                case '0':
                    let profile = await user.getProfileInfo();
                    await user.showProfile(profile);
                    await client.sendText(roomId, i18n.t(["general", "menu"]));
                    break;
                case '1':
                    await user.setState('view_profiles');
                    await user.showRandomProfile();
                    break;
                case '2':
                    await user.showNewLikes();
                    await client.sendText(roomId, i18n.t(["general", "menu"]));
                    break;
                case '3':
                    let unreadMessages = await user.getUnreadMessages();
                    if (!unreadMessages || unreadMessages.length == 0) {
                        await client.sendText(roomId, i18n.t(["general", "nonewmessages"]));
                        return;
                    }
                    await client.sendText(roomId, i18n.t(["general", "msg"]));

                    for (let message of unreadMessages) {
                        await client.sendText(roomId, i18n.t(["general", "showmessage"], { user: message.mx_id }));

                        if (message.type == "t") {
                            await client.sendText(roomId, message.content)
                        } else if (message.type == "p" || message.type == "v") {
                            let msgtype;

                            if (message.type == "p") {
                                msgtype = "m.image";
                            } else if (message.type == "v") {
                                msgtype = "m.video";
                            }

                            await client.sendMessage(roomId, {
                                msgtype: msgtype,
                                body: "Message",
                                url: message.content
                            });
                        }
                        await user.markMessageAsRead(message.sender);
                    }
                    await client.sendText(roomId, i18n.t(["general", "menu"]));
                    break;
                case '4':
                    await user.setState('location-u');
                    await client.sendText(roomId, i18n.t(["setup", "location"]));
                    break;
                case '5':
                    await user.setState('range-u');
                    await client.sendText(roomId, i18n.t(["setup", "range"]));
                    break;
                case '6':
                    await user.setState('name-u');
                    await client.sendText(roomId, i18n.t(["setup", "name"]));
                    break;
                case '7':
                    await user.setState('age-u');
                    await client.sendText(roomId, i18n.t(["setup", "age"]));
                    break;
                case '8':
                    await user.setState('sex-u');
                    await client.sendText(roomId, i18n.t(["setup", "sex"]));
                    break;
                case '9':
                    await user.setState('interest-u');
                    await client.sendText(roomId, i18n.t(["setup", "interest"]));
                    break;
                case '10':
                    await user.setState('description-u');
                    await client.sendText(roomId, i18n.t(["setup", "description"]));
                    break;
                case '11':
                    await user.erasePfp();
                    await user.setState('pictures-u');
                    await client.sendText(roomId, i18n.t(["setup", "pictures"]));
                    break;
                case '12':
                    await user.setState('language-u');
                    await client.sendText(roomId, i18n.t(["setup", "language"]));
                    break;
                default:
                    await client.sendText(roomId, i18n.t(["errors", "didntunderstand"]));
                    await client.sendText(roomId, i18n.t(["general", "menu"]));
                    break;
            }
            break;
        default:
            await client.sendText(roomId, i18n.t(["errors", "didntunderstand"]));
            return;

    }
});

client.on("room.event", async (roomId, event) => {
    try {
        const user = new User(roomId);

        if (event.type === "m.room.member" && event.content?.membership === "leave") {
            await user.fullErase();
            logInfo(`Bot has left a room with ID ${roomId}`);
            client.leaveRoom(roomId);
        }
    } catch (e) {
        logError(e);
    }
});

client.on("room.invite", async (roomId, event) => {
    try {
        await client.joinRoom(roomId);

        let members = await client.getAllRoomMembers(roomId)
        let isDM = members.length == 2 ? true : false;

        if (!isDM) {
            await client.sendText(roomId, i18n.t(["errors", "notadm"]));
            await client.leaveRoom(roomId);
        }

        const i18n = new I18n({
            en: JSON.parse(fs.readFileSync("./translations/en.json")),
            ru: JSON.parse(fs.readFileSync("./translations/ru.json"))
        });

        logInfo(`Bot has joined a room with ID ${roomId}`);

        let mx_id = event.sender;
        const user = new User(roomId);
        if (!await user.exists()) {
            await user.init(mx_id);

            i18n.locale = "en";
            await client.sendText(roomId, i18n.t(["general", "welcome"]));
            i18n.locale = "ru";
            await client.sendText(roomId, i18n.t(["general", "welcome"]));
        } else {
            user.setState('view_profiles');
        }
    } catch (e) {
        logError(e);
    }
});

client.start().then(() => logInfo("Bot started!"));

export { client };