import Config                                                                                                                                                                                 from '../config/Config';
import { StatusCodes }                                                                                                                                                                        from 'http-status-codes';
import GitlabVisibility                                                                                                                                                                       from '../shared/types/Gitlab/GitlabVisibility';
import express                                                                                                                                                                                from 'express';
import SharedConfig                                                                                                                                                                           from '../shared/config/SharedConfig';
import { CommitSchema, ExpandedUserSchema, Gitlab, MemberSchema, ProjectBadgeSchema, ProjectSchema, ReleaseSchema, RepositoryFileExpandedSchema, RepositoryFileSchema, RepositoryTreeSchema } from '@gitbeaker/rest';
import logger                                                                                                                                                                                 from '../shared/logging/WinstonLogger';
import { AccessLevel, EditProjectOptions, ProjectVariableSchema, ProtectedBranchAccessLevel, ProtectedBranchSchema }                                                                          from '@gitbeaker/core';
import DojoStatusCode                                                                                                                                                                         from '../shared/types/Dojo/DojoStatusCode';
import SharedGitlabManager                                                                                                                                                                    from '../shared/managers/SharedGitlabManager';


class GitlabManager extends SharedGitlabManager {
    constructor() {
        super(Config.gitlab.account.token);
    }

    getUserProfile(token: string): Promise<ExpandedUserSchema> | undefined {
        try {
            const profileApi = new Gitlab({
                                              host      : SharedConfig.gitlab.URL,
                                              oauthToken: token
                                          });

            return profileApi.Users.showCurrentUser();
        } catch ( e ) {
            return undefined;
        }
    }

    getRepositoryMembers(idOrNamespace: string): Promise<Array<MemberSchema>> {
        return this.api.ProjectMembers.all(idOrNamespace, { includeInherited: true });
    }

    getRepositoryReleases(repoId: number): Promise<Array<ReleaseSchema>> {
        return this.api.ProjectReleases.all(repoId);
    }

    async getRepositoryLastCommit(repoId: number, branch: string = 'main'): Promise<CommitSchema | undefined> {
        try {
            const commits = await this.api.Commits.all(repoId, {
                refName : branch,
                maxPages: 1,
                perPage : 1
            });

            return commits.length > 0 ? commits[0] : undefined;
        } catch ( e ) {
            logger.error(JSON.stringify(e));
            return undefined;
        }
    }

    createRepository(name: string, description: string, visibility: 'public' | 'internal' | 'private', initializeWithReadme: boolean, namespace: number, sharedRunnersEnabled: boolean, wikiEnabled: boolean, importUrl: string): Promise<ProjectSchema> {
        return this.api.Projects.create({
                                            name                : name,
                                            description         : description,
                                            importUrl           : importUrl,
                                            initializeWithReadme: initializeWithReadme,
                                            namespaceId         : namespace,
                                            sharedRunnersEnabled: sharedRunnersEnabled,
                                            visibility          : visibility,
                                            wikiAccessLevel     : wikiEnabled ? 'enabled' : 'disabled'
                                        });
    }

    deleteRepository(repoId: number): Promise<void> {
        return this.api.Projects.remove(repoId);
    }

    forkRepository(forkId: number, name: string, path: string, description: string, visibility: 'public' | 'internal' | 'private', namespace: number): Promise<ProjectSchema> {
        return this.api.Projects.fork(forkId, {
            name       : name,
            path       : path,
            description: description,
            namespaceId: namespace,
            visibility : visibility
        });
    }

    editRepository(repoId: number, newAttributes: EditProjectOptions): Promise<ProjectSchema> {
        return this.api.Projects.edit(repoId, newAttributes);
    }

    changeRepositoryVisibility(repoId: number, visibility: GitlabVisibility): Promise<ProjectSchema> {
        return this.editRepository(repoId, { visibility: visibility });
    }

    addRepositoryMember(repoId: number, userId: number, accessLevel: Exclude<AccessLevel, AccessLevel.ADMIN>): Promise<MemberSchema> {
        return this.api.ProjectMembers.add(repoId, userId, accessLevel);
    }

    addRepositoryVariable(repoId: number, key: string, value: string, isProtected: boolean, isMasked: boolean): Promise<ProjectVariableSchema> {
        return this.api.ProjectVariables.create(repoId, key, value, {
            variableType: 'env_var',
            protected   : isProtected,
            masked      : isMasked
        });
    }

    addRepositoryBadge(repoId: number, linkUrl: string, imageUrl: string, name: string): Promise<ProjectBadgeSchema> {
        return this.api.ProjectBadges.add(repoId, linkUrl, imageUrl, {
            name: name
        });
    }

    async checkTemplateAccess(projectIdOrNamespace: string, req: express.Request, res?: express.Response): Promise<boolean> {
        // Get the Gitlab project and check if it have public or internal visibility
        try {
            const project: ProjectSchema = await this.getRepository(projectIdOrNamespace);

            if ( [ 'public', 'internal' ].includes(project.visibility) ) {
                req.session.sendResponse(res, StatusCodes.OK);
                return true;
            }
        } catch ( e ) {
            req.session.sendResponse(res, StatusCodes.NOT_FOUND, undefined, 'Template not found', DojoStatusCode.GITLAB_TEMPLATE_NOT_FOUND);
            return false;
        }

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

        if ( isUsersAtLeastReporter.user && isUsersAtLeastReporter.dojo ) {
            req.session.sendResponse(res, StatusCodes.OK);
            return true;
        } else {
            req.session.sendResponse(res, StatusCodes.UNAUTHORIZED, undefined, 'Template access unauthorized', DojoStatusCode.GITLAB_TEMPLATE_ACCESS_UNAUTHORIZED);
            return false;
        }
    }

    protectBranch(repoId: number, branchName: string, allowForcePush: boolean, allowedToMerge: ProtectedBranchAccessLevel, allowedToPush: ProtectedBranchAccessLevel, allowedToUnprotect: ProtectedBranchAccessLevel): Promise<ProtectedBranchSchema> {
        return this.api.ProtectedBranches.protect(repoId, branchName, {
            allowForcePush      : allowForcePush,
            mergeAccessLevel    : allowedToMerge,
            pushAccessLevel     : allowedToPush,
            unprotectAccessLevel: allowedToUnprotect
        });
    }

    getRepositoryTree(repoId: number, recursive: boolean = true, branch: string = 'main'): Promise<Array<RepositoryTreeSchema>> {
        return this.api.Repositories.allRepositoryTrees(repoId, {
            recursive: recursive,
            ref      : branch
        });
    }

    getFile(repoId: number, filePath: string, branch: string = 'main'): Promise<RepositoryFileExpandedSchema> {
        return this.api.RepositoryFiles.show(repoId, filePath, branch);
    }

    private createUpdateFile(create: boolean, repoId: number, filePath: string, fileBase64: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined): Promise<RepositoryFileSchema> {
        const gitFunction = create ? this.api.RepositoryFiles.create.bind(this.api) : this.api.RepositoryFiles.edit.bind(this.api);

        return gitFunction(repoId, filePath, branch, fileBase64, commitMessage, {
            encoding   : 'base64',
            authorName : authorName,
            authorEmail: authorMail
        });
    }

    createFile(repoId: number, filePath: string, fileBase64: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined): Promise<RepositoryFileSchema> {
        return this.createUpdateFile(true, repoId, filePath, fileBase64, commitMessage, branch, authorName, authorMail);
    }

    updateFile(repoId: number, filePath: string, fileBase64: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined): Promise<RepositoryFileSchema> {
        return this.createUpdateFile(false, repoId, filePath, fileBase64, commitMessage, branch, authorName, authorMail);
    }

    deleteFile(repoId: number, filePath: string, commitMessage: string, branch: string = 'main', authorName: string = 'Dojo', authorMail: string | undefined = undefined): Promise<void> {
        return this.api.RepositoryFiles.remove(repoId, filePath, branch, commitMessage, {
            authorName : authorName,
            authorEmail: authorMail
        });
    }
}


export default new GitlabManager();