import axios             from 'axios';
import Config            from '../config/Config';
import GitlabRepository  from '../shared/types/Gitlab/GitlabRepository';
import GitlabAccessLevel from '../shared/types/Gitlab/GitlabAccessLevel';
import GitlabMember      from '../shared/types/Gitlab/GitlabMember';
import { StatusCodes }   from 'http-status-codes';
import GitlabVisibility  from '../shared/types/Gitlab/GitlabVisibility';
import ApiRequest        from '../types/ApiRequest';
import GitlabUser        from '../shared/types/Gitlab/GitlabUser';
import GitlabRoutes      from '../shared/types/Gitlab/GitlabRoutes';


class GitlabManager {
    private getApiUrl(route: GitlabRoutes): string {
        return `${ Config.gitlab.apiURL }${ route }`;
    }

    private async getGitlabUser(paramToSearch: string | number, paramName: string): Promise<GitlabUser | undefined> {
        try {
            const params: any = {};
            params[paramName] = paramToSearch;
            return (await axios.get<Array<GitlabUser>>(this.getApiUrl(GitlabRoutes.USERS_GET), { params: params })).data[0];
        } catch ( e ) { }

        return undefined;
    }

    public async getUserById(id: number): Promise<GitlabUser | undefined> {
        return await this.getGitlabUser(id, 'id');
    }

    public async getUserByUsername(username: string): Promise<GitlabUser | undefined> {
        return await this.getGitlabUser(username, 'search');
    }

    async getRepository(idOrNamespace: string): Promise<GitlabRepository> {
        const response = await axios.get<GitlabRepository>(this.getApiUrl(GitlabRoutes.REPOSITORY_GET).replace('{{id}}', encodeURIComponent(idOrNamespace)));

        return response.data;
    }

    async getRepositoryMembers(idOrNamespace: string): Promise<Array<GitlabMember>> {
        const response = await axios.get<Array<GitlabMember>>(this.getApiUrl(GitlabRoutes.REPOSITORY_MEMBERS_GET).replace('{{id}}', encodeURIComponent(idOrNamespace)));

        return response.data;
    }

    async createRepository(name: string, description: string, visibility: string, initializeWithReadme: boolean, namespace: number, sharedRunnersEnabled: boolean, wikiEnabled: boolean, import_url: string): Promise<GitlabRepository> {
        const response = await axios.post<GitlabRepository>(this.getApiUrl(GitlabRoutes.REPOSITORY_CREATE), {
            name                  : name,
            description           : description,
            import_url            : import_url,
            initialize_with_readme: initializeWithReadme,
            namespace_id          : namespace,
            shared_runners_enabled: sharedRunnersEnabled,
            visibility            : visibility,
            wiki_enabled          : wikiEnabled
        });

        return response.data;
    }

    async forkRepository(forkId: number, name: string, path: string, description: string, visibility: string, namespace: number): Promise<GitlabRepository> {
        const response = await axios.post<GitlabRepository>(this.getApiUrl(GitlabRoutes.REPOSITORY_FORK).replace('{{id}}', String(forkId)), {
            name        : name,
            path        : path,
            description : description,
            namespace_id: namespace,
            visibility  : visibility
        });

        return response.data;
    }

    async addRepositoryMember(repoId: number, userId: number, accessLevel: GitlabAccessLevel): Promise<GitlabMember> {
        const response = await axios.post<GitlabMember>(this.getApiUrl(GitlabRoutes.REPOSITORY_MEMBER_ADD).replace('{{id}}', String(repoId)), {
            user_id     : userId,
            access_level: accessLevel
        });

        return response.data;
    }

    async checkTemplateAccess(idOrNamespace: string, req: ApiRequest): Promise<StatusCodes> {
        // Get the Gitlab project and check if it have public or internal visibility
        try {
            const project: GitlabRepository = await this.getRepository(idOrNamespace);

            if ( [ GitlabVisibility.Public.valueOf(), GitlabVisibility.Internal.valueOf() ].includes(project.visibility) ) {
                return StatusCodes.OK;
            }
        } catch ( e ) {
            return StatusCodes.NOT_FOUND;
        }

        // Check if the user and dojo are members (with at least reporter access) of the project
        const members = await this.getRepositoryMembers(idOrNamespace);
        const isUsersAtLeastReporter = {
            user: false,
            dojo: false
        };
        members.forEach(member => {
            if ( member.access_level >= GitlabAccessLevel.REPORTER ) {
                if ( member.id === req.session.profile.gitlabId ) {
                    isUsersAtLeastReporter.user = true;
                } else if ( member.id === Config.gitlab.account.id ) {
                    isUsersAtLeastReporter.dojo = true;
                }
            }
        });

        return isUsersAtLeastReporter.user && isUsersAtLeastReporter.dojo ? StatusCodes.OK : StatusCodes.UNAUTHORIZED;
    }
}


export default new GitlabManager();