const should = require('should');
const supertest = require('supertest');
const testUtils = require('../../utils');
const config = require('../../../core/shared/config');
const db = require('../../../core/server/data/db');
const models = require('../../../core/server/models');
const localUtils = require('./utils');

describe('User API', function () {
    let request;
    let inactiveUser;
    let admin;

    before(async function () {
        await localUtils.startGhost();
        request = supertest.agent(config.get('url'));

        // create inactive user
        inactiveUser = await testUtils.createUser({
            user: testUtils.DataGenerator.forKnex.createUser({email: 'test+3@ghost.org', status: 'inactive'}),
            role: testUtils.DataGenerator.Content.roles[2].name
        });

        // create admin user
        admin = await testUtils.createUser({
            user: testUtils.DataGenerator.forKnex.createUser({email: 'test+admin@ghost.org', slug: 'admin'}),
            role: testUtils.DataGenerator.Content.roles[0].name
        });

        // by default we login with the owner
        await localUtils.doAuth(request, 'posts');
    });

    it('Can request all users ordered by id', async function () {
        // @NOTE: ASC is default
        const res = await request.get(localUtils.API.getApiQuery('users/?order=id%20DESC'))
            .set('Origin', config.get('url'))
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        should.not.exist(res.headers['x-cache-invalidate']);

        const jsonResponse = res.body;
        should.exist(jsonResponse.users);
        localUtils.API.checkResponse(jsonResponse, 'users');

        // owner use + ghost-author user when Ghost starts
        // and two extra users, see createUser in before
        jsonResponse.users.should.have.length(4);

        localUtils.API.checkResponse(jsonResponse.users[0], 'user');

        jsonResponse.users[0].email.should.eql(admin.email);
        jsonResponse.users[0].status.should.eql(admin.status);

        jsonResponse.users[1].email.should.eql(inactiveUser.email);
        jsonResponse.users[1].status.should.eql(inactiveUser.status);

        jsonResponse.users[2].email.should.eql('ghost-author@example.com');
        jsonResponse.users[3].email.should.eql(testUtils.DataGenerator.Content.users[0].email);

        testUtils.API.isISO8601(jsonResponse.users[3].last_seen).should.be.true();
        testUtils.API.isISO8601(jsonResponse.users[3].created_at).should.be.true();
        testUtils.API.isISO8601(jsonResponse.users[3].updated_at).should.be.true();

        // only "ghost" and joe-bloggs author has a published post
        jsonResponse.users[0].url.should.eql(`${config.get('url')}/404/`);
        jsonResponse.users[1].url.should.eql(`${config.get('url')}/404/`);
        jsonResponse.users[2].url.should.eql(`${config.get('url')}/author/ghost/`);
        jsonResponse.users[3].url.should.eql(`${config.get('url')}/author/joe-bloggs/`);
    });

    it('Can include user roles', async function () {
        const res = await request.get(localUtils.API.getApiQuery('users/?include=roles'))
            .set('Origin', config.get('url'))
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        should.not.exist(res.headers['x-cache-invalidate']);
        const jsonResponse = res.body;
        should.exist(jsonResponse.users);
        localUtils.API.checkResponse(jsonResponse, 'users');

        jsonResponse.users.should.have.length(4);
        localUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']);
    });

    it('Can paginate users', async function () {
        const res = await request.get(localUtils.API.getApiQuery('users/?page=2'))
            .set('Origin', config.get('url'))
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        const jsonResponse = res.body;
        should.equal(jsonResponse.meta.pagination.page, 2);
    });

    it('Can retrieve a user by id', async function () {
        const res = await request.get(localUtils.API.getApiQuery('users/' + testUtils.getExistingData().users[0].id + '/?include=roles,roles.permissions,count.posts'))
            .set('Origin', config.get('url'))
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        should.not.exist(res.headers['x-cache-invalidate']);
        const jsonResponse = res.body;
        should.exist(jsonResponse.users);
        should.not.exist(jsonResponse.meta);

        jsonResponse.users.should.have.length(1);
        localUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles', 'count']);
        localUtils.API.checkResponse(jsonResponse.users[0].roles[0], 'role', ['permissions']);

        should.exist(jsonResponse.users[0].count.posts);
        jsonResponse.users[0].count.posts.should.equal(8);
    });

    it('Can retrieve a user by slug', async function () {
        const res = await request.get(localUtils.API.getApiQuery('users/slug/joe-bloggs/'))
            .set('Origin', config.get('url'))
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        should.not.exist(res.headers['x-cache-invalidate']);
        const jsonResponse = res.body;
        should.exist(jsonResponse.users);
        should.not.exist(jsonResponse.meta);

        jsonResponse.users.should.have.length(1);
        localUtils.API.checkResponse(jsonResponse.users[0], 'user');
    });

    it('Can retrieve a user by email', async function () {
        const res = await request.get(localUtils.API.getApiQuery('users/email/jbloggs%40example.com/'))
            .set('Origin', config.get('url'))
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        should.not.exist(res.headers['x-cache-invalidate']);
        const jsonResponse = res.body;
        should.exist(jsonResponse.users);
        should.not.exist(jsonResponse.meta);

        jsonResponse.users.should.have.length(1);
        localUtils.API.checkResponse(jsonResponse.users[0], 'user');
    });

    it('can edit a user', async function () {
        const res = await request.put(localUtils.API.getApiQuery('users/me/'))
            .set('Origin', config.get('url'))
            .send({
                users: [{
                    website: 'http://joe-bloggs.ghost.org',
                    password: 'mynewfancypasswordwhichisnotallowed'
                }]
            })
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        const putBody = res.body;
        res.headers['x-cache-invalidate'].should.eql('/*');
        should.exist(putBody.users[0]);
        putBody.users[0].website.should.eql('http://joe-bloggs.ghost.org');
        putBody.users[0].email.should.eql('jbloggs@example.com');
        localUtils.API.checkResponse(putBody.users[0], 'user');

        should.not.exist(putBody.users[0].password);

        try {
            const user = await models.User.findOne({id: putBody.users[0].id});
            await models.User.isPasswordCorrect({
                plainPassword: 'mynewfancypasswordwhichisnotallowed',
                hashedPassword: user.get('password')
            });
            return Promise.reject();
        } catch (err) {
            err.code.should.eql('PASSWORD_INCORRECT');
        }
    });

    it('can edit a user fetched from the API', async function () {
        const userToEditId = testUtils.getExistingData().users[1].id;
        const res = await request
            .get(localUtils.API.getApiQuery(`users/${userToEditId}/?include=roles`))
            .set('Origin', config.get('url'))
            .expect(200);

        const jsonResponse = res.body;
        jsonResponse.users[0].name.should.equal('Ghost');

        should.exist(jsonResponse.users[0].roles);
        jsonResponse.users[0].roles.should.have.length(1);
        jsonResponse.users[0].roles[0].name.should.equal('Contributor');

        jsonResponse.users[0].name = 'Changed Name';

        const editResponse = await request
            .put(localUtils.API.getApiQuery(`users/${userToEditId}/?include=roles`))
            .set('Origin', config.get('url'))
            .send({
                users: jsonResponse.users
            })
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        const editJSONResponse = editResponse.body;
        editJSONResponse.users[0].name.should.equal('Changed Name');

        should.exist(editJSONResponse.users[0].roles);
        editJSONResponse.users[0].roles.should.have.length(1);
        editJSONResponse.users[0].roles[0].name.should.equal('Contributor');
    });

    it('Can edit user with empty roles data and does not change the role', async function () {
        const res = await request.put(localUtils.API.getApiQuery('users/me?include=roles'))
            .set('Origin', config.get('url'))
            .send({
                users: [{
                    roles: []
                }]
            })
            .expect(200);

        const jsonResponse = res.body;
        should.exist(jsonResponse.users);
        jsonResponse.users.should.have.length(1);

        should.exist(jsonResponse.users[0].roles);
        jsonResponse.users[0].roles.should.have.length(1);
        jsonResponse.users[0].roles[0].name.should.equal('Owner');
    });

    it('Cannot edit user with invalid roles data', async function () {
        const userId = testUtils.getExistingData().users[1].id;
        const res = await request.put(localUtils.API.getApiQuery(`users/${userId}?include=roles`))
            .set('Origin', config.get('url'))
            .send({
                users: [{
                    roles: ['Invalid Role Name']
                }]
            })
            .expect(422);

        const jsonResponse = res.body;
        should.exist(jsonResponse.errors);
        jsonResponse.errors.should.have.length(1);
        jsonResponse.errors[0].message.should.match(/cannot edit user/);
    });

    it('Can edit user roles by name', async function () {
        const userId = testUtils.getExistingData().users[1].id;
        const res = await request.put(localUtils.API.getApiQuery(`users/${userId}?include=roles`))
            .set('Origin', config.get('url'))
            .send({
                users: [{
                    roles: ['Administrator']
                }]
            })
            .expect(200);

        const jsonResponse = res.body;
        should.exist(jsonResponse.users);
        jsonResponse.users.should.have.length(1);

        should.exist(jsonResponse.users[0].roles);
        jsonResponse.users[0].roles.should.have.length(1);
        jsonResponse.users[0].roles[0].name.should.equal('Administrator');
    });

    it('Does not trigger cache invalidation when a private attribute on a user has been changed', async function () {
        const res = await request.put(localUtils.API.getApiQuery('users/me/'))
            .set('Origin', config.get('url'))
            .send({
                users: [{
                    comment_notifications: false
                }]
            })
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        should.equal(res.headers['x-cache-invalidate'], undefined);
    });

    it('Does not trigger cache invalidation when no attribute on a user has been changed', async function () {
        const res = await request.put(localUtils.API.getApiQuery('users/me/'))
            .set('Origin', config.get('url'))
            .send({
                users: [{
                    facebook: null
                }]
            })
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        should.equal(res.headers['x-cache-invalidate'], undefined);
    });

    it('Can destroy an active user and transfer posts to the owner', async function () {
        const userId = testUtils.getExistingData().users[1].id;
        const userSlug = testUtils.getExistingData().users[1].slug;
        const ownerId = testUtils.getExistingData().users[0].id;

        const res = await request
            .get(localUtils.API.getApiQuery(`posts/?filter=authors:${userSlug}`))
            .set('Origin', config.get('url'))
            .expect(200);

        res.body.posts.length.should.eql(7);

        const ownerPostsAuthorsModels = await db.knex('posts_authors')
            .where({
                author_id: ownerId
            })
            .select();

        // includes posts & pages
        should.equal(ownerPostsAuthorsModels.length, 8);

        const userPostsAuthorsModels = await db.knex('posts_authors')
            .where({
                author_id: userId
            })
            .select();

        // includes posts & pages
        should.equal(userPostsAuthorsModels.length, 11);

        const res2 = await request
            .delete(localUtils.API.getApiQuery(`users/${userId}`))
            .set('Origin', config.get('url'))
            .expect(200);

        should.exist(res2.body.meta.filename);

        await request
            .get(localUtils.API.getApiQuery(`db/?filename=${res2.body.meta.filename}/`))
            .set('Origin', config.get('url'))
            .expect(200);

        await request
            .get(localUtils.API.getApiQuery(`users/${userId}/`))
            .set('Origin', config.get('url'))
            .expect(404);

        const res3 = await request
            .get(localUtils.API.getApiQuery(`posts/?filter=authors:${userSlug}}`))
            .set('Origin', config.get('url'))
            .expect(200);

        res3.body.posts.length.should.eql(0);

        const rolesUsersModels = await db.knex('roles_users')
            .where({
                user_id: userId
            })
            .select();

        rolesUsersModels.length.should.eql(0);

        const rolesUsers = await db.knex('roles_users').select();
        rolesUsers.length.should.greaterThan(0);

        const ownerPostsAuthorsModelsAfter = await db.knex('posts_authors')
            .where({
                author_id: ownerId
            })
            .select();

        should.equal(ownerPostsAuthorsModelsAfter.length, 19);

        const userPostsAuthorsModelsAfter = await db.knex('posts_authors')
            .where({
                author_id: userId
            })
            .select();

        should.equal(userPostsAuthorsModelsAfter.length, 0);
    });

    it('Can transfer ownership to admin user', async function () {
        const res = await request
            .put(localUtils.API.getApiQuery('users/owner'))
            .set('Origin', config.get('url'))
            .send({
                owner: [{
                    id: admin.id
                }]
            })
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        res.body.users[0].roles[0].name.should.equal(testUtils.DataGenerator.Content.roles[0].name);
        res.body.users[1].roles[0].name.should.equal(testUtils.DataGenerator.Content.roles[3].name);
    });

    it('Can change password and retain the session', async function () {
        let res = await request
            .put(localUtils.API.getApiQuery('users/password'))
            .set('Origin', config.get('url'))
            .send({
                password: [{
                    newPassword: '1234abcde!!',
                    ne2Password: '1234abcde!!',
                    oldPassword: 'Sl1m3rson99',
                    user_id: testUtils.getExistingData().users[0].id
                }]
            })
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        should.exist(res.body.password);
        should.exist(res.body.password[0].message);

        await request
            .get(localUtils.API.getApiQuery('session/'))
            .set('Origin', config.get('url'))
            .expect(200);
    });

    it('Can read the user\'s Personal Token', async function () {
        const res = await request
            .get(localUtils.API.getApiQuery('users/me/token/'))
            .set('Origin', config.get('url'))
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        should.exist(res.body.apiKey);
        should.exist(res.body.apiKey.id);
        should.exist(res.body.apiKey.secret);
    });

    it('Can\'t read another user\'s Personal Token', async function () {
        const userNotAdmin = testUtils.getExistingData().users.find(user => user.email === 'ghost-author@example.com');
        const res = await request
            .get(localUtils.API.getApiQuery('users/' + userNotAdmin.id + '/token/'))
            .set('Origin', config.get('url'))
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(403);

        should.exist(res.body.errors);
    });

    it('Can re-generate the user\'s Personal Token', async function () {
        const {body: {apiKey: {id, secret}}} = await request
            .get(localUtils.API.getApiQuery('users/me/token/'))
            .set('Origin', config.get('url'))
            .expect(200);

        const res = await request
            .put(localUtils.API.getApiQuery('users/me/token'))
            .set('Origin', config.get('url'))
            .expect('Content-Type', /json/)
            .expect('Cache-Control', testUtils.cacheRules.private)
            .expect(200);

        should.exist(res.body.apiKey);
        should.exist(res.body.apiKey.id);
        should.exist(res.body.apiKey.secret);
        should.not.exist(res.headers['x-cache-invalidate']);

        should(res.body.id).not.be.equal(id);
        should(res.body.secret).not.be.equal(secret);
    });
});
