import axios                                                 from 'axios';
import SharedConfig                                          from '../config/SharedConfig';
import * as GitlabCore                                       from '@gitbeaker/core';
import { GitbeakerRequestError }                             from '@gitbeaker/requester-utils';
import { Gitlab, PipelineSchema, ProjectSchema, UserSchema } from '@gitbeaker/rest';
import GitlabToken                                           from '../types/Gitlab/GitlabToken';


class SharedGitlabManager {
    protected api!: GitlabCore.Gitlab<false>;
    private readonly refreshTokenFunction?: () => Promise<string>;

    setToken(token: string) {
        this.api = new Gitlab(Object.assign({
                                                host: SharedConfig.gitlab.URL
                                            }, this.refreshTokenFunction ? { oauthToken: token } : { token: token }));
    }

    constructor(token: string, refreshTokenFunction?: () => Promise<string>) {
        this.refreshTokenFunction = refreshTokenFunction;
        this.setToken(token);
    }

    protected async executeGitlabRequest<T>(request: () => Promise<T>, refreshTokenIfNeeded: boolean = true): Promise<T> {
        try {
            return await request();
        } catch ( error ) {
            if ( this.refreshTokenFunction && refreshTokenIfNeeded && error instanceof GitbeakerRequestError ) {
                this.setToken(await this.refreshTokenFunction());

                return this.executeGitlabRequest(request, false);
            } else {
                throw error;
            }
        }
    }

    async getTokens(codeOrRefresh: string, isRefresh: boolean = false, clientSecret: string = ''): Promise<GitlabToken> {
        const response = await axios.post<GitlabToken>(SharedConfig.login.gitlab.url.token, {
            client_id    : SharedConfig.login.gitlab.client.id,
            client_secret: clientSecret,
            grant_type   : isRefresh ? 'refresh_token' : 'authorization_code',
            refresh_token: codeOrRefresh,
            code         : codeOrRefresh,
            redirect_uri : SharedConfig.login.gitlab.url.redirect
        });

        return response.data;
    }

    public async getUserById(id: number): Promise<UserSchema | undefined> {
        try {
            return await this.executeGitlabRequest(async () => {
                const user = await this.api.Users.show(id);

                return user.id === id ? user : undefined;
            });
        } catch ( e ) {
            return undefined;
        }
    }

    public async getUserByUsername(username: string): Promise<UserSchema | undefined> {
        try {
            return await this.executeGitlabRequest(async () => {
                const user = await this.api.Users.all({
                                                          username: username,
                                                          maxPages: 1,
                                                          perPage : 1
                                                      });


                return user.length > 0 && user[0].username === username ? user[0] : undefined;
            });
        } catch ( e ) {
            return undefined;
        }
    }

    async getRepository(projectIdOrNamespace: string): Promise<ProjectSchema> {
        return this.executeGitlabRequest(() => this.api.Projects.show(projectIdOrNamespace));
    }

    async getRepositoryPipelines(repoId: number, branch: string = 'main'): Promise<Array<PipelineSchema>> {
        return this.executeGitlabRequest(() => this.api.Pipelines.all(repoId, { ref: branch }));
    }
}


export default SharedGitlabManager;