package com.onsemi.middleware.UECommon.SCM.gitlab;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.apache.commons.lang.StringUtils;
import org.gitlab4j.api.GitLabApi;
import org.gitlab4j.api.GitLabApiException;
import org.gitlab4j.api.models.Commit;
import org.gitlab4j.api.models.CommitRef;
import org.gitlab4j.api.models.CompareResults;
import org.gitlab4j.api.models.Group;
import org.gitlab4j.api.models.MergeRequest;
import org.gitlab4j.api.models.Project;
import org.gitlab4j.api.models.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;

import com.onsemi.middleware.UECommon.SCM.contracts.SCMClient;
import com.onsemi.middleware.UECommon.SCM.contracts.SCMCommit;
import com.onsemi.middleware.UECommon.SCM.contracts.SCMPullRequest;
import com.onsemi.middleware.UECommon.SCM.contracts.SCMRepository;
import com.onsemi.middleware.UECommon.SCM.contracts.SCMTag;
import com.onsemi.middleware.UECommon.SCM.gitlab.iterator.GitLabBranchesIterator;
import com.onsemi.middleware.UECommon.SCM.gitlab.iterator.GitLabCommitIterator;
import com.onsemi.middleware.UECommon.SCM.gitlab.iterator.GitLabTagIterator;
import com.onsemi.middleware.UECommon.SCM.gitlab.model.GitLabBranch;
import com.onsemi.middleware.UECommon.SCM.gitlab.model.GitLabCommit;
import com.onsemi.middleware.UECommon.SCM.gitlab.model.GitLabProject;
import com.onsemi.middleware.UECommon.SCM.gitlab.model.GitLabPullRequest;
import com.onsemi.middleware.UECommon.SCM.gitlab.model.GitLabTag;

public class GitLabClient implements SCMClient, InitializingBean {

    public static final int PAGE_SIZE = 100;
    public static final String GITLAB_REGEX_PATTERN = "gitlab\\.com/(.*?)\\.git";

    private GitLabApi gitLabApi;
    private final Logger log = LoggerFactory.getLogger(this.getClass());

    @Value("${config.dashboard.scm.gitlab.GITLAB_URL:https://gitlab.com}")
    private String GITLAB_URL;

    @Value("${config.dashboard.scm.gitlab.UE_GITLAB_TOKEN}")
    private String UE_GITLAB_TOKEN;

    public GitLabClient() {

    }

    /**
     * Creates new project under the provided group.
     * @param repositoryName new name of the project
     * @param projectKey project key, under which the new project will be created. Example: onsemi2/it-ea/mdw/ue/framework
     * @return {@link SCMRepository}
     */
    @Override
    public SCMRepository createRepository(String repositoryName, String projectKey) {
        try {
            List<Group> groups = this.getGitLabApi().getGroupApi().getGroups(projectKey);
            Optional<Group> optionalGroup = groups
                    .stream()
                    .filter(g -> StringUtils.containsIgnoreCase(g.getFullPath(), projectKey))
                    .findFirst();

            if(optionalGroup.isEmpty()){
                throw new GitLabApiException("Searched group could not be found. Name: "+projectKey);
            }

            Project project = this.gitLabApi.getProjectApi().createProject(optionalGroup.get().getId(),repositoryName);
            return new GitLabProject(project);
        } catch (GitLabApiException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Iterator<GitLabTag> getTagsFor(String repository) {
        try {
            String projectPath = extractProjectPath(repository);
            return new GitLabTagIterator(this,projectPath, PAGE_SIZE);
        } catch (GitLabApiException e) {
            log.error("Error while getting tags from GitLab for the repository " + repository,e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public Iterator<GitLabCommit> getCommitsFor(String repository) {
        try {
            String projectPath = extractProjectPath(repository);
            return new GitLabCommitIterator(this,projectPath, PAGE_SIZE);
        } catch (GitLabApiException e) {
            log.error("Error while getting commits from GitLab for the repository " + repository,e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public Iterator<GitLabBranch> getBranchesFor(String repository) {
        try {
            String projectPath = extractProjectPath(repository);
            return new GitLabBranchesIterator(this,projectPath, PAGE_SIZE);
        } catch (GitLabApiException e) {
            log.error("Error while getting branches from GitLab for the repository " + repository,e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public SCMPullRequest getPullRequest(String repository, String pullRequestId) {
        try {
            String projectPath = extractProjectPath(repository);
            MergeRequest mergeRequest = this.getGitLabApi().getMergeRequestApi().getMergeRequest(projectPath, Long.parseLong(pullRequestId));
            return new GitLabPullRequest(mergeRequest);
        } catch (GitLabApiException e) {
            log.error("Error while getting tags from GitLab for the repository " + repository + "¨for the given pullRequestId " + pullRequestId,e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public List<? extends SCMCommit> getCommitsForBranch(String repository, String branchName) {
        try {
            String projectPath = extractProjectPath(repository);
            return this.getGitLabApi().getCommitsApi().getCommits(projectPath,branchName,null).stream().map(GitLabCommit::new).collect(Collectors.toList());
        } catch (GitLabApiException e) {
            log.error("Error while getting commits for the branch " + branchName +" for the repository " + repository,e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public List<? extends SCMCommit> getCommitsBetweenBranches(String repository, String fromBranch, String toBranch) {
        try {
            String projectPath = extractProjectPath(repository);
            CompareResults compare = this.gitLabApi.getRepositoryApi().compare(projectPath, fromBranch, toBranch);
            return compare.getCommits().stream().map(GitLabCommit::new).collect(Collectors.toList());
        } catch (GitLabApiException e) {
            log.error("Error while getting commits between branches from GitLab for the repository " + repository,e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public SCMCommit getOneCommitFor(String repository, String commitId) {
        try {
            String projectPath = extractProjectPath(repository);
            Commit commit = this.gitLabApi.getCommitsApi().getCommit(projectPath, commitId);
            return new GitLabCommit(commit);
        } catch (GitLabApiException e) {
            log.error("Error while getting commit with id " + commitId + "for the repository " + repository,e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean setTagOnCommit(String commitId, String tagMsg, String repository) {
        try {
            String projectPath = extractProjectPath(repository);
            this.gitLabApi.getTagsApi().createTag(projectPath,tagMsg,commitId);
            return true;
        } catch (GitLabApiException e) {
            log.error("Error setting tag on commit " + commitId + " for the repository " + repository,e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public Collection<SCMTag> getTagsForCommit(String repository, String commitId) {
        String projectPath = extractProjectPath(repository);
		try {
            Commit commit = this.gitLabApi.getCommitsApi()
                                          .getCommit(projectPath, commitId);
            return this.gitLabApi.getCommitsApi().getCommitRefs(projectPath, commitId, CommitRef.RefType.TAG)
                    .stream()
                    .map(ref -> {
                        Tag origTag = new Tag();
                        origTag.setCommit(commit);
                        origTag.setName(ref.getName());
                        GitLabTag tag = new GitLabTag(origTag);
                        return tag;
                    }).collect(Collectors.toList());

		} catch (GitLabApiException e) {
            log.error("Error while getting commit tags/branches with commitId " + commitId + "for the repository " + repository,e);
            throw new RuntimeException(e);
		}
	}

    @Override
    public void afterPropertiesSet() throws Exception {
        this.gitLabApi = new GitLabApi(GITLAB_URL, UE_GITLAB_TOKEN);
    }

    public GitLabApi getGitLabApi(){
        return this.gitLabApi;
    }

    private String extractProjectPath(String repository) {
        Pattern pattern = Pattern.compile(GITLAB_REGEX_PATTERN);

        Matcher matcher = pattern.matcher(repository);

        if (matcher.find()) {
            return matcher.group(1);
        } else {
            log.error("Project path couldn't be extracted from the repository URL: " + repository);
            throw new NoSuchElementException("Project path couldn't be extracted from the repository URL: " + repository);
        }

    }
}
