import { client } from "./index.js";
import { sequence, max_length, getCityNameByID, getCountryNameByID } from "./utils.js";
import { I18n } from "i18n-js";
import fs from "fs";
import { db } from "./db.js";

export class User {
    constructor(roomId) {
        this.roomId = roomId
        this.i18n = new I18n({
            en: JSON.parse(fs.readFileSync("./translations/en.json")),
            ru: JSON.parse(fs.readFileSync("./translations/ru.json"))
        });
        this.i18n.locale = "en";
    }

    async go(one, two) {
        await client.sendText(this.roomId, this.i18n.t([one, two]));
        await this.setState(two);
    }

    async exists() {
        return (await db.query("SELECT EXISTS (SELECT * FROM users WHERE room_id = $1)", [this.roomId])).rows[0].exists;
    }

    async init(mxid) {
        await db.query("INSERT INTO users(mx_id, room_id, current_action) VALUES ($1, $2, $3)", [mxid, this.roomId, "wait_start"]);
    }

    async setState(state) {
        await db.query("UPDATE users SET current_action = $1 WHERE room_id = $2", [state, this.roomId]);
    }

    async set(property, value) {
        //${property} is passed from the code, not user input. It's safe. Maybe. I don't know ;)
        await db.query(`UPDATE users SET ${property} = $1 WHERE room_id = $2`, [value, this.roomId]);
    }

    async getLanguage() {
        const language = (await db.query("SELECT language FROM users WHERE room_id = $1", [this.roomId])).rows[0].language;
        this.i18n.locale = language
        return language;
    }

    async getCurrentAction() {
        return (await db.query('SELECT current_action FROM users WHERE room_id = $1', [this.roomId])).rows[0].current_action;
    }

    async process(answer) {
        const current_action = await this.getCurrentAction();

        let next_action = "menu";
        if (sequence[current_action]) next_action = sequence[current_action]
        if (max_length[current_action] < answer.length) {
            await client.sendText(this.roomId, this.i18n.t(["error", "toobig"]));
            await client.sendText(this.roomId, this.i18n.t(["setup", current_action]));
            return;
        }
        await this.set(current_action, answer);
        await client.sendText(this.roomId, this.i18n.t(["general", "setopt"], { opt: answer }));

        await this.go(next_action == "menu" ? "general" : "setup", next_action);
    }

    async update(answer) {
        const i18n = new I18n({
            en: JSON.parse(fs.readFileSync("./translations/en.json")),
            ru: JSON.parse(fs.readFileSync("./translations/ru.json"))
        });
        i18n.locale = await this.getLanguage();

        //when updating a field, current_action = <action>-u.
        const current_action = (await this.getCurrentAction()).split("-")[0];

        if (max_length[current_action] < answer.length) {
            await client.sendText(this.roomId, i18n.t(["error", "toobig"]));
            await client.sendText(this.roomId, i18n.t(["setup", current_action]));
            return;
        }
        await this.set(current_action, answer);
        await this.markProfileAsUnRead();
        await this.getLanguage()
        await client.sendText(this.roomId, i18n.t(["general", "setopt"], { opt: answer }));
        await this.go("general", "menu");
    }

    async getProfilePictures() {
        return (await db.query("SELECT url, type FROM media WHERE owner = $1 AND purpose = 'p'", [this.roomId])).rows;
    }

    async addProfilePicture(type, mxc) {
        await db.query("INSERT INTO media (owner, type, purpose, url) VALUES ($1, $2, 'p', $3)", [this.roomId, type, mxc]);
    }

    async getProfileInfo() {
        let user = (await db.query("SELECT mx_id, room_id, name, age, sex, description, location FROM users WHERE room_id = $1", [this.roomId])).rows[0];
        if (!user) return null;
        const u = new User(user.room_id);
        const media = await u.getProfilePictures();
        user.media = media;
        return user;
    }

    async markProfileAsRead(whoRead) {
        await db.query("INSERT INTO read_profiles(sender, recipient) VALUES ($1, $2)", [whoRead, this.roomId]);
    }

    async markProfileAsUnRead() {
        await db.query("DELETE FROM read_profiles WHERE recipient = $1", [this.roomId]);
    }

    async selectRandomProfile() {
        const { myrange, myinterest, mysex, myage, mycity } = (await db.query(`SELECT range AS myrange, 
                interest AS myinterest, 
                sex AS mysex, 
                age AS myage, 
                location AS mycity 
            FROM users WHERE room_id = $1`, [this.roomId])
        ).rows[0];

        const { lat, lng } = (await db.query("SELECT lat, lng FROM cities WHERE ID = $1", [mycity])).rows[0];
        //TODO: figure out how to move it to PG function
        let user = (await db.query(`SELECT 
            room_id, name, age, sex, description, location, range FROM users
        WHERE
            age::numeric <@ ANY(ARRAY[numrange($1 - 2, $1 + 2)])
        AND room_id != $2
        AND ${myinterest !== 'b' ? "sex = $3" : "$3 = $3 AND $4 = $4 AND $5 = $5 AND $6 = $6 AND $7 = $7"}
        AND ${myrange !== 0 ?
                `location = ANY(ARRAY(SELECT ID 
            FROM cities 
            WHERE 
            check_distance($6::double precision, $7::double precision, lat, lng, $5::double precision)
            AND (check_distance($6::double precision, $7::double precision, lat, lng, range) OR range = 0)
            ))`
                :
                `range = 0 OR
            check_distance($6::double precision, $7::double precision, (SELECT lat FROM cities WHERE ID = location), (SELECT lng FROM cities WHERE ID = location), range)`
            }
        AND (interest = $4 OR interest = 'b')
        AND NOT (room_id = ANY(ARRAY(SELECT recipient FROM read_profiles)))
        ORDER BY RANDOM()
        LIMIT 1`, [myage, this.roomId, myinterest, mysex, myrange, lat, lng])
        ).rows[0];

        if (!user) return null;
        const u = new User(user.room_id);
        const media = await u.getProfilePictures();
        u.markProfileAsRead(this.roomId);

        user.media = media;
        return user;
    }

    async showProfile(profile) {
        if (!profile) {
            await client.sendText(this.roomId, this.i18n.t(["errors", "noprofiles"]));
            return;
        }

        profile.country = await getCountryNameByID(profile.location);
        profile.city = await getCityNameByID(profile.location);

        let message =
            `${profile.country}, ${profile.city}.
${profile.name}, ${profile.sex == 'm' ? 'male' : 'female'}, ${profile.age}.
${profile.description}`;

        await client.sendText(this.roomId, message);

        if (profile.media) {
            for (let media of profile.media) {
                let msgtype;
                if (media.type == 'p') {
                    msgtype = "m.image";
                } else if (media.type == 'v') {
                    msgtype = "m.video";
                }
                await client.sendMessage(this.roomId, {
                    msgtype: msgtype,
                    body: "Profile media",
                    url: media.url
                });
            }
        }
    }

    async showRandomProfile() {
        let profile = await this.selectRandomProfile();
        await this.showProfile(profile);
        await client.sendText(roomId, i18n.t(["general", "rate"]));
    }

    async getCurrentlyViewingProfile() {
        return (await db.query("SELECT currently_viewing FROM users WHERE room_id = $1", [this.roomId])).rows[0].currently_viewing;
    }

    async addLikeTo(like) {
        await db.query(`DELETE FROM likes l1
            USING likes l2
            WHERE l1.sender = l2.recipient
            AND l1.recipient = l2.sender
            AND l1.read = TRUE
            AND l2.read = TRUE`);

        await db.query("INSERT INTO likes (sender, recipient) VALUES ($1, $2) ON CONFLICT DO NOTHING", [this.roomId, like]);
    }

    async checkForMutualLikeWith(person) {
        return (await db.query(`SELECT COUNT(*) > 0 AS value
                           FROM likes l1
                           JOIN likes l2 ON l1.sender = l2.recipient AND l1.recipient = l2.sender
                           WHERE (l1.read = FALSE OR l2.read = FALSE)
                           AND l1.sender = $1
                           AND l1.recipient = $2`, [this.roomId, person])).rows[0].value;
    }

    async getLikes() {
        return (await db.query("SELECT sender FROM likes WHERE recipient = $1 AND read = FALSE", [this.roomId])).rows;
    }

    async markLikeAsRead(sender) {
        await db.query("UPDATE likes SET read = TRUE WHERE sender = $1 AND recipient = $2", [sender, this.roomId]);
    }

    async showNewLikes() {
        let likes = (await this.getLikes());
        for (let liked of likes) {
            if (await this.checkForMutualLikeWith(liked.sender)) {
                await this.showProfile(liked.sender);
                await this.markLikeAsRead(liked.sender);
            }
        }
    }

    async markMessageAsRead(sender) {
        return (await db.query("UPDATE messages SET read = TRUE WHERE sender = $1 AND recipient = $2", [sender, this.roomId]));
    }

    async getUnreadMessages() {
        return (await db.query("SELECT (SELECT mx_id FROM users WHERE room_id = sender LIMIT 1), sender, type, content FROM messages WHERE recipient = $1 AND read = FALSE", [this.roomId])).rows
    }

    async erasePfp() {
        await db.query("DELETE FROM media WHERE owner = $1 AND purpose = 'p'", [this.roomId]);
    }

    async eraseMessages() {
        await db.query("DELETE FROM messages WHERE sender = $1 OR recipient = $1", [this.roomId]);
    }

    async eraseMedia() {
        await db.query("DELETE FROM media WHERE owner = $1", [this.roomId]);
    }

    async eraseLikes() {
        await db.query("DELETE FROM likes WHERE sender = $1 OR recipient = $1", [this.roomId]);
    }

    async fullErase() {
        await this.eraseMessages()
        await this.eraseMedia()
        await this.eraseLikes()
    }
}
