const axios = require('axios');
let request = require('request-promise').defaults({jar: true});
const cheerio = require('cheerio');
const chalk = require('chalk');

/**
 * A new moodle user object which is used to fetch info for a specific person.  Can be linked to different moodle websites.
 */
class MoodleUser {

    /**
     * Create a new moodle user object
     * @param {String} username - The users moodle username
     * @param {String} password - The users moodle password
     * @param {String} moodleURL - The moodle websites address
     */
    constructor(username = '', password = '', moodleURL = '') {

        this.username = username;
        this.password = password;
        this.moodleURL = (moodleURL.endsWith('/') ? moodleURL.slice(0, -1) : `${moodleURL}`);

        this.loggedIn = false;
        this.loginReTrys = 0;
    }

    /**
     * Returns the current users login status
     * @return {Boolean} - If the user object is currently logged in.
     */
    get status() {
        return this.loggedIn;
    }

    /**
     * Returns the last login time
     * @return {Date} The time since the last login
     */
    get lastLogin() {
        return this.login ? Date.now() - this.login : null;
    }

    /**
     * Returns the cached user grades
     * @return {Array<Object>} - The users cached grades.
     */
    get grades() {
        return this.moduleGrades;
    }

    /**
     * Returns the cached user modules
     * @return {Array<Object>} - The users cached modules.
     */
    get modules() {
        return this.userModules;
    }

    /**
     * Returns the user info
     * @return {Array<Object>} - The users cached personal information.
     */
    get info() {
        return this.userInfo
    }

    static _statusCheck(res) {
        if (!(res.status >= 200 && res.status < 300)) return console.error(`Unable to fetch user data, ${chalk.red(`Status Code: ${res.statusText}`)}`);
    }

    /**
     * Will attempt log the account in.  Recalling this method will refresh the auth token.
     * @async
     * @return {Promise<Boolean>} - Weather or not the login was successful
     */
    async login() {
        try {
            let res = await request.post({
                url: `${this.moodleURL}/login/index.php`,
                followAllRedirects: true,
                form: {username: this.username, password: this.password},
                resolveWithFullResponse: true
            });
            this.cookie = res.request.headers.cookie; // Stupid website only returns a 200 status code for everything #SmartPeople

            this.loggedIn = !!(this.cookie); // If we can't get a login token we haven't logged in

            if (!this.loggedIn) {
                this.loginReTrys++;
                return this.loggedIn;
            }

            this.login = Date.now();
            return this.loggedIn;

        } catch (err) {
            // If the actual post fails
            this.loggedIn = false;
            this.loginReTrys++;
            return this.loggedIn;
        }
    }

    /**
     * Logs the current user out.  Can be logged back in refreshing the auth token.
     * @async
     * @return {Promise<Boolean>} - The current logged in status of the account.
     */
    async logout() {
        try {
            await this._checkLogin();

            let res = await axios.get(`${this.moodleURL}/user/profile.php`, {headers: {Cookie: this.cookie}});
            MoodleUser._statusCheck(res);

            this.loggedIn = false;
            this.cookie = '';
            return this.loggedIn;

        } catch (err) {
            console.error(`Unable to log user ${chalk.green(this.username)} out! Error: ${chalk.red(err.name)}`);
        }
    }

    /**
     * Fetches the users information and stores it in the cache
     * @async
     * @return {Promise<Object>}
     */
    async fetchUserInfo() {
        try {
            await this._checkLogin();

            let res = await axios.get(`${this.moodleURL}/user/profile.php`, {headers: {Cookie: this.cookie}});
            MoodleUser._statusCheck(res);

            const $ = cheerio.load(res.data);
            this.userInfo = {
                'name': $('.usertext').text(),
                'avatar': $('.userpicture').first().attr('src')
            };

            return this.userInfo;
        } catch (err) {
            return {};
        }
    }

    /**
     * Fetches the users course modules and stores it in the cache
     * @async
     * @return {Promise<Array(Object)>}
     */
    async fetchModules() {
        try {
            await this._checkLogin();

            let res = await axios.get(`${this.moodleURL}/user/profile.php`, {headers: {Cookie: this.cookie}});
            MoodleUser._statusCheck(res);

            const $ = cheerio.load(res.data);
            this.userModules = $('.contentnode').children().first().children().last().children().first().children().map((i, elem) => $(elem).text()).get() || [];
            return this.userModules;
        } catch (err) {
            return [];
        }
    }

    /**
     * Fetches the users module grades and stores it in the cache
     * @async
     * @return {Promise<Array(Object)>}
     */
    async fetchGrades() {
        try {
            await this._checkLogin();

            let res = await axios.get(`${this.moodleURL}/grade/report/overview/index.php`, {headers: {Cookie: this.cookie}});
            MoodleUser._statusCheck(res);

            const $ = cheerio.load(res.data);
            this.moduleGrades = $('#overview-grade').children().last().children().not('.emptyrow').map((i, elem) => {
                return {name: $(elem).children().first().text(), value: $(elem).children().last().text()}
            }).get();

            return this.moduleGrades;
        } catch (err) {
            return [];
        }
    }

    /**
     * Fetches the users blog posts and stores them in the cache
     * @async
     * @return {Promise<Array(Object)>}
     */
    async fetchBlogPosts() {
        try {
            await this._checkLogin();

            let res = await axios.get(`${this.moodleURL}/blog/index.php`, {headers: {Cookie: this.cookie}});
            MoodleUser._statusCheck(res);

            const $ = cheerio.load(res.data);
            this.blogPosts = $('.blog_entry').map((i, elem) => {
                return {
                    subject: $(elem).children().first().children().last().children().first().text(),
                    author: $(elem).children().first().children().last().children().last().text(),
                    authorPicture: $(elem).children().first().children().first().children().first().children().first().attr('src'),
                    text: $(elem).children().last().children().first().children().filter('.no-overflow').text()
                }
            }).get();

            return this.blogPosts;

        } catch (err) {
            return [];
        }
    }

    /**
     * Fetches the users upcoming calender and stores it in the cache
     * @async
     * @return {Promise<Array(Object)>}
     */
    async fetchCalender() {
        try {
            await this._checkLogin();

            let res = await axios.get(`${this.moodleURL}/calendar/view.php`, {headers: {Cookie: this.cookie}});
            MoodleUser._statusCheck(res);

            const $ = cheerio.load(res.data);
            this.calender = $('.eventlist').children().map((i, elem) => {
                return {
                    name: $(elem).children().filter('.referer').text(),
                    course: $(elem).children().filter('.course').text(),
                    date: $(elem).children().filter('.date').text(),
                    description: $(elem).children().filter('.description').text()
                };
            }).get();

            return this.calender;
        } catch (err) {
            return [];
        }
    }

    /**
     * Fetches the users sessions and stores it in the cache
     * @async
     * @return {Promise<Array(Object)>}
     */
    async fetchSessions() {
        try {
            await this._checkLogin();

            let res = await axios.get(`${this.moodleURL}/report/usersessions/user.php`, {headers: {Cookie: this.cookie}});
            MoodleUser._statusCheck(res);

            const $ = cheerio.load(res.data);
            this.sessions = $('.generaltable').children().last().children().map((i, elem) => {
                return {
                    date: $(elem).children().filter('.c0').text(),
                    lastAccess: $(elem).children().filter('.c1').text(),
                    ip: $(elem).children().filter('.c2').text()
                }
            }).get();

            return this.sessions;

        } catch (err) {
            return [];
        }
    }

    async _checkLogin() {
        try {
            if (!this.loggedIn) {
                return console.log(`Unable to login user ${chalk.red(this.username)} for website ${chalk.red(this.moodleURL)}\n${chalk.green(`Make sure you've called {MoodleUser}.login() first!`)}`);

            }

        } catch (err) {
            console.error(`Error handling re-login attempts, Error: ${err.stack}`);
        }
    }
}

module.exports = {
    MoodleUser: MoodleUser
};