const Model = require('flitter-orm/src/model/Model')
const { ObjectId } = require('mongodb')
const User = require('../auth/User.model')
const ValidScope = require('../scopes/Valid.scope')
const uuid = require('uuid/v4')
const jwt = require('jsonwebtoken')

/*
 * Token Model
 * -------------------------------------------------------------
 * Put some description here!
 */
class Token extends Model {
    static #default_grants = ['database']

    static get services() {
        return [...super.services, 'configs']
    }

    static get schema() {
        // Return a flitter-orm schema here.
        return {
            user_id: ObjectId,
            token: String,
            issued: { type: Date, default: () => new Date },
            expires: {
                type: Date,
                default: () => {
                    const d = new Date
                    d.setDate(d.getDate() + 7)
                    return d
                },
            },
            valid: { type: Boolean, default: true },
            grants: [String],
        }
    }

    static scopes = [new ValidScope]

    static async create(user) {
        const token = new this({ user_id: user._id, grants: this.#default_grants })
        await token.save()
        token.token = token.generate()
        return await token.save()
    }

    static async for_user(user) {
        const token = await this.findOne({ user_id: user._id })
        return token ? token : await this.create(user)
    }

    static verify(token) {
        const secret = this.prototype.configs.get('server.session.secret')
        const server = this.prototype.configs.get('app.url')

        return new Promise((res, rej) => {
            jwt.verify(token, secret, (err, decoded) => {
                if ( err ) rej(err)
                else if ( decoded.iss !== server ) rej(new Error('Invalid token issuer: '+decoded.iss))
                else {
                    this.findOne({ user_id: ObjectId(decoded.sub) }).then(result => {
                        if ( !result ) rej(new Error('Unable to find associated token. It may have been manually invalidated.'))
                        else res(result)
                    })
                }
            })
        })
    }

    generate() {
        const secret = this.configs.get('server.session.secret')
        const server = this.configs.get('app.url')

        const payload = {
            sub: String(this.user_id),
            iss: server,
            permissions: this.grants.join(','),
            exp: (this.expires.getTime()/1000 | 0),
            iat: (this.issued.getTime()/1000 | 0),
        }

        return jwt.sign(payload, secret)
    }

    user() {
        return this.has_one(User, 'user_id', '_id')
    }

    can(grant) {
        return this.grants.includes(grant)
    }
}

module.exports = exports = Token
