import { mergeConfig } from '@vendure/core';
import gql from 'graphql-tag';
import path from 'path';

import { initialData } from '../../../e2e-common/e2e-initial-data';
import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
import { createTestEnvironment } from '../../testing/lib/create-test-environment';

import {
    sync,
    TestPluginWithCustomPermissions,
    wishlist,
} from './fixtures/test-plugins/with-custom-permissions';
import {
    AdministratorFragment,
    CreateAdministrator,
    CreateRole,
    RoleFragment,
    UpdateRole,
} from './graphql/generated-e2e-admin-types';
import { CREATE_ADMINISTRATOR, CREATE_ROLE, UPDATE_ROLE } from './graphql/shared-definitions';
import { assertThrowsWithMessage } from './utils/assert-throws-with-message';

describe('Custom permissions', () => {
    const { server, adminClient } = createTestEnvironment(
        mergeConfig(testConfig(), {
            plugins: [TestPluginWithCustomPermissions],
        }),
    );

    let testRole: RoleFragment;
    let testAdmin: AdministratorFragment;

    beforeAll(async () => {
        await server.init({
            initialData,
            productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
            customerCount: 1,
        });
        await adminClient.asSuperAdmin();

        // create a new role and Admin and sign in as that Admin
        const { createRole } = await adminClient.query<CreateRole.Mutation, CreateRole.Variables>(
            CREATE_ROLE,
            {
                input: {
                    channelIds: ['T_1'],
                    code: 'test-role',
                    description: 'Testing custom permissions',
                    permissions: [],
                },
            },
        );
        testRole = createRole;
        const { createAdministrator } = await adminClient.query<
            CreateAdministrator.Mutation,
            CreateAdministrator.Variables
        >(CREATE_ADMINISTRATOR, {
            input: {
                firstName: 'Test',
                lastName: 'Admin',
                emailAddress: 'test@admin.com',
                password: 'test',
                roleIds: [testRole.id],
            },
        });

        testAdmin = createAdministrator;
    }, TEST_SETUP_TIMEOUT_MS);

    afterAll(async () => {
        await server.destroy();
    });

    describe('superadmin has custom permissions automatically', () => {
        beforeAll(async () => {
            await adminClient.asSuperAdmin();
        });

        it('single permission', async () => {
            const { syncWishlist } = await adminClient.query(SYNC);
            expect(syncWishlist).toBe(true);
        });

        it('CRUD create permission', async () => {
            const { createWishlist } = await adminClient.query(CRUD_CREATE);
            expect(createWishlist).toBe(true);
        });

        it('CRUD read permission', async () => {
            // tslint:disable-next-line:no-shadowed-variable
            const { wishlist } = await adminClient.query(CRUD_READ);
            expect(wishlist).toBe(true);
        });

        it('CRUD update permission', async () => {
            const { updateWishlist } = await adminClient.query(CRUD_UPDATE);
            expect(updateWishlist).toBe(true);
        });

        it('CRUD delete permission', async () => {
            const { deleteWishlist } = await adminClient.query(CRUD_DELETE);
            expect(deleteWishlist).toBe(true);
        });
    });

    describe('custom permissions prevent unauthorized access', () => {
        beforeAll(async () => {
            await adminClient.asUserWithCredentials(testAdmin.emailAddress, 'test');
        });

        it(
            'single permission',
            assertThrowsWithMessage(async () => {
                await adminClient.query(SYNC);
            }, 'You are not currently authorized to perform this action'),
        );

        it(
            'CRUD create permission',
            assertThrowsWithMessage(async () => {
                await adminClient.query(CRUD_CREATE);
            }, 'You are not currently authorized to perform this action'),
        );

        it(
            'CRUD read permission',
            assertThrowsWithMessage(async () => {
                await adminClient.query(CRUD_READ);
            }, 'You are not currently authorized to perform this action'),
        );

        it(
            'CRUD update permission',
            assertThrowsWithMessage(async () => {
                await adminClient.query(CRUD_UPDATE);
            }, 'You are not currently authorized to perform this action'),
        );

        it(
            'CRUD delete permission',
            assertThrowsWithMessage(async () => {
                await adminClient.query(CRUD_DELETE);
            }, 'You are not currently authorized to perform this action'),
        );
    });

    describe('adding permissions enables access', () => {
        beforeAll(async () => {
            await adminClient.asSuperAdmin();
            await adminClient.query<UpdateRole.Mutation, UpdateRole.Variables>(UPDATE_ROLE, {
                input: {
                    id: testRole.id,
                    permissions: [
                        sync.Permission,
                        wishlist.Create,
                        wishlist.Read,
                        wishlist.Update,
                        wishlist.Delete,
                    ],
                },
            });

            await adminClient.asUserWithCredentials(testAdmin.emailAddress, 'test');
        });

        it('single permission', async () => {
            const { syncWishlist } = await adminClient.query(SYNC);
            expect(syncWishlist).toBe(true);
        });

        it('CRUD create permission', async () => {
            const { createWishlist } = await adminClient.query(CRUD_CREATE);
            expect(createWishlist).toBe(true);
        });

        it('CRUD read permission', async () => {
            // tslint:disable-next-line:no-shadowed-variable
            const { wishlist } = await adminClient.query(CRUD_READ);
            expect(wishlist).toBe(true);
        });

        it('CRUD update permission', async () => {
            const { updateWishlist } = await adminClient.query(CRUD_UPDATE);
            expect(updateWishlist).toBe(true);
        });

        it('CRUD delete permission', async () => {
            const { deleteWishlist } = await adminClient.query(CRUD_DELETE);
            expect(deleteWishlist).toBe(true);
        });
    });
});

const SYNC = gql`
    mutation Sync {
        syncWishlist
    }
`;

const CRUD_READ = gql`
    query CrudRead {
        wishlist
    }
`;

const CRUD_CREATE = gql`
    mutation CrudCreate {
        createWishlist
    }
`;

const CRUD_UPDATE = gql`
    mutation CrudUpdate {
        updateWishlist
    }
`;

const CRUD_DELETE = gql`
    mutation CrudDelete {
        deleteWishlist
    }
`;
