/**
 * Copyright (c) 2020 Gitpod GmbH. All rights reserved.
 * Licensed under the GNU Affero General Public License (AGPL).
 * See License-AGPL.txt in the project root for license information.
 */

import * as chai from 'chai';
const expect = chai.expect;
import { suite, test, timeout } from 'mocha-typescript';

import { Identity, Workspace, WorkspaceInstance } from '@gitpod/gitpod-protocol';
import { testContainer } from './test-container';
import { DBIdentity } from './typeorm/entity/db-identity';
import { TypeORMUserDBImpl } from './typeorm/user-db-impl';
import { TypeORMWorkspaceDBImpl } from './typeorm/workspace-db-impl';
import { TypeORM } from './typeorm/typeorm';
import { DBUser } from './typeorm/entity/db-user';
import { DBWorkspace } from './typeorm/entity/db-workspace';
import { DBWorkspaceInstance } from './typeorm/entity/db-workspace-instance';

@suite class UserDBSpec {

    db = testContainer.get<TypeORMUserDBImpl>(TypeORMUserDBImpl);

    wsDb = testContainer.get<TypeORMWorkspaceDBImpl>(TypeORMWorkspaceDBImpl);

    async before() {
        await this.wipeRepos();
    }

    async after() {
        await this.wipeRepos();
    }

    async wipeRepos() {
        const typeorm = testContainer.get<TypeORM>(TypeORM);
        const mnr = await typeorm.getConnection();
        await mnr.getRepository(DBUser).delete({});
        await mnr.getRepository(DBWorkspace).delete({});
        await mnr.getRepository(DBWorkspaceInstance).delete({});
    }

    readonly _IDENTITY1: Identity = {
        authProviderId: "GitHub",
        authId: "1234",
        authName: "gero",
        deleted: false,
        primaryEmail: undefined,
        readonly: false
    };
    readonly _IDENTITY2: Identity = {
        authProviderId: "GitHub",
        authId: "4321",
        authName: "gero",
        deleted: false,
        primaryEmail: undefined,
        readonly: false
    };
    readonly _WRONG_ID = '123';    // no uuid
    // Copy to avoid pollution
    get IDENTITY1() { return Object.assign({}, this._IDENTITY1); }
    get IDENTITY2() { return Object.assign({}, this._IDENTITY2); }
    get WRONG_ID() { return Object.assign({}, this._WRONG_ID); }

    @test(timeout(10000))
    public async createUserAndFindById() {
        let user = await this.db.newUser();
        user.identities.push(this.IDENTITY1);
        user = await this.db.storeUser(user)

        const dbResult = await this.db.findUserById(user.id)
        // We use 'user' as reference, so clean it
        // @ts-ignore
        user.identities.forEach(i => delete (i as DBIdentity).user);
        expect(dbResult).to.deep.include(user);
    }

    @test(timeout(10000))
    public async createUserAndNotFindByWrongId() {
        let user = await this.db.newUser();
        user.identities.push(this.IDENTITY2);
        user = await this.db.storeUser(user)
        expect(await this.db.findUserById(this.WRONG_ID)).to.be.undefined;
    }

    @test(timeout(10000))
    public async createUserAndFindByIdentity() {
        let user = await this.db.newUser();
        user.identities.push(this.IDENTITY1);
        user = await this.db.storeUser(user)

        const dbResult = await this.db.findUserByIdentity(this.IDENTITY1);
        // We use 'user' as reference, so clean it
        // @ts-ignore
        user.identities.forEach(i => delete (i as DBIdentity).user);
        expect(dbResult).to.deep.include(user);
    }

    @test(timeout(10000))
    public async findUsersByEmail_multiple_users_identities() {
        let user1 = await this.db.newUser();
        user1.name = "Old";
        user1.identities.push(TestData.ID1);
        user1.identities.push(TestData.ID2);
        user1.identities.push(TestData.ID3);
        user1 = await this.db.storeUser(user1)

        await this.wsDb.store({ ...TestData.DEFAULT_WS, id: "1", creationTime: new Date().toISOString(),  ownerId: user1.id });
        await this.wsDb.storeInstance({ ...TestData.DEFAULT_WSI, workspaceId: "1", id: "11", creationTime: new Date().toISOString() })

        // ensure that the second user's last modified is definitely after first one's
        await new Promise(resolve => setTimeout(resolve, 100));

        let user2 = await this.db.newUser();
        user2.name = "New";
        user2.identities.push(TestData.ID4);
        user2.identities.push(TestData.ID5);
        user2 = await this.db.storeUser(user2)

        await this.wsDb.store({ ...TestData.DEFAULT_WS, id: "2", creationTime: new Date().toISOString(),  ownerId: user2.id });
        await this.wsDb.storeInstance({ ...TestData.DEFAULT_WSI, workspaceId: "2", id: "22", creationTime: new Date().toISOString() })

        const dbResult = await this.db.findUsersByEmail(TestData.primaryEmail);

        expect(dbResult).to.be.an("array");
        expect(dbResult).to.be.have.length(2);
        expect(dbResult[0].name).to.be.eq("New");
        expect(dbResult[1].name).to.be.eq("Old");
    }

    @test(timeout(10000))
    public async findUserByIdentity_after_moving_identity() {
        let user1 = await this.db.newUser();
        user1.name = "ABC";
        user1.identities.push(TestData.ID1);
        user1.identities.push(TestData.ID2);
        user1 = await this.db.storeUser(user1)

        let user2 = await this.db.newUser();
        user2.name = "XYZ";
        user2.identities.push(TestData.ID3);
        user2 = await this.db.storeUser(user2)
        user2.identities.push(TestData.ID2);
        user2 = await this.db.storeUser(user2)

        const r2 = await this.db.findUserByIdentity(TestData.ID1);
        expect(r2).to.be.not.undefined;
        expect(r2!.identities).to.have.length(1);

        const r1 = await this.db.findUserByIdentity(TestData.ID2);
        expect(r1).to.be.not.undefined;
        expect(r1!.name).to.be.eq("XYZ");
    }
}

namespace TestData {
    export const primaryEmail = "foo@bar.com";
    const DEFAULT: Identity = {
        authProviderId: "Public-GitHub",
        primaryEmail,
        authId: "1234",
        authName: "Foo Bar",
        deleted: false,
        readonly: false
    };
    export const ID1: Identity = { ...DEFAULT, authId: "2345" };
    export const ID2: Identity = { ...DEFAULT, authId: "3456", authProviderId: "Public-GitLab" };
    export const ID3: Identity = { ...DEFAULT, authId: "4567", authProviderId: "ACME" };
    export const ID4: Identity = { ...DEFAULT, authId: "5678" };
    export const ID5: Identity = { ...DEFAULT, authId: "6789", authProviderId: "ACME" };
    export const DEFAULT_WS: Workspace = {
        id: '1',
        type: 'regular',
        creationTime: new Date().toISOString(),
        config: {
            ports: [],
            image: '',
            tasks: []
        },
        context: { title: 'example' },
        contextURL: 'example.org',
        description: 'blabla',
        ownerId: '12345'
    };
    export const DEFAULT_WSI: WorkspaceInstance = {
        workspaceId: DEFAULT_WS.id,
        id: '123',
        ideUrl: 'example.org',
        region: 'unknown',
        workspaceImage: 'abc.io/test/image:123',
        creationTime: new Date().toISOString(),
        startedTime: undefined,
        deployedTime: undefined,
        stoppedTime: undefined,
        status: {
            phase: "preparing",
            conditions: {},
        },
        configuration: {
            theiaVersion: "unknown",
            ideImage: "unknown"
        },
        deleted: false
    };
}

module.exports = new UserDBSpec()
