package com.atlassian.webdriver.bitbucket.page;

import com.atlassian.bitbucket.commit.CommitListMergeFilter;
import com.atlassian.pageobjects.elements.ElementBy;
import com.atlassian.pageobjects.elements.PageElement;
import com.atlassian.pageobjects.elements.query.Poller;
import com.atlassian.webdriver.bitbucket.element.CommitList;
import com.atlassian.webdriver.bitbucket.util.UrlUtils;
import org.openqa.selenium.By;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import static com.atlassian.bitbucket.util.UrlUtils.buildQueryParams;
import static java.util.Optional.of;

public class CommitListPage extends BranchLayoutPage {

    private static final String COMMIT_ID_ATTRIBUTE = "data-commitid";
    private static final String COMMIT_ROW_SELECTOR = ".commit-row";
    private static final String COMMIT_TAG_ATTRIBUTE = "data-names";
    private static final String COMMIT_TAG_SELECTOR = ".tags-container .tag";

    @ElementBy(id = "commits-table", pageElementClass = CommitList.class)
    private CommitList commitList;

    @ElementBy(className = "commits-table-spinner")
    private PageElement commitsSpinner;

    private final CommitListMergeFilter mergeFilter;
    private final String until;

    public CommitListPage(String projectKey, String repoSlug) {
        this(projectKey, repoSlug, null, null);
    }

    public CommitListPage(String projectKey, String repoSlug, CommitListMergeFilter mergeFilter) {
        this(projectKey, repoSlug, mergeFilter, null);
    }

    public CommitListPage(String projectKey, String repoSlug, String until) {
        this(projectKey, repoSlug, null, until);
    }

    public CommitListPage(String projectKey, String repoSlug, CommitListMergeFilter mergeFilter, String until) {
        super(projectKey, repoSlug);
        this.mergeFilter = mergeFilter;
        this.until = until;
    }

    public CommitList getCommitList() {
        return commitList;
    }

    public String getBranchNameFromUrl() {
        return UrlUtils.getReferenceNameFromUrlParameter("until", driver.getCurrentUrl());
    }

    @Override
    public String getUrl() {
        String queryParams = "?" + buildQueryParams(
                "until", until == null ? null : until,
                "merges", mergeFilter == null ? null : mergeFilter.name());
        return "/projects/" + projectKey + "/repos/" + slug + "/commits" + queryParams;
    }

    /**
     * Get tag display ids (i.e. unqualified) for a single given commit hash.
     *
     * @param commitId  a commit hash
     * @return a list of zero or more tags associated with the provided commit hash
     */
    public List<String> getTagsFor(String commitId) {
        Poller.waitUntilTrue(commitList.find(By.cssSelector(COMMIT_TAG_SELECTOR)).timed().isVisible());
        List<String> tags = new ArrayList<>();

        getRowForCommit(commitId).ifPresent(row -> {
            for (PageElement tagSelector : row.findAll(By.cssSelector(COMMIT_TAG_SELECTOR))) {
                String attr = tagSelector.getAttribute(COMMIT_TAG_ATTRIBUTE);
                if (attr != null) {
                    tags.addAll(Arrays.asList(attr.split(":")));
                }
            }
        });

        return tags;
    }

    public CommitListPage waitUntilCommitsTableLoaded() {
        Poller.waitUntilFalse(commitsSpinner.timed().isVisible());
        return this;
    }

    /**
     * @param commitId  the commit hash to find the row for
     * @return          an {@link Optional} containing the {@link PageElement} for the row if found, otherwise
     *                  {@link Optional#empty()} if no such commit can be found
     */
    private Optional<PageElement> getRowForCommit(String commitId) {
        List<PageElement> elements = getCommitList().findAll(By.cssSelector(COMMIT_ROW_SELECTOR));
        for (PageElement row : elements) {
            String attr = row.getAttribute(COMMIT_ID_ATTRIBUTE);
            if (attr != null && attr.contains(commitId)) {
                return of(row);
            }
        }
        return Optional.empty();
    }
}
