package com.atlassian.webdriver.bitbucket.page;

import com.atlassian.bitbucket.pull.PullRequestState;
import com.atlassian.pageobjects.elements.ElementBy;
import com.atlassian.pageobjects.elements.Options;
import com.atlassian.pageobjects.elements.PageElement;
import com.atlassian.pageobjects.elements.SelectElement;
import com.atlassian.pageobjects.elements.query.Poller;
import com.atlassian.webdriver.bitbucket.element.PullRequestTable;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import org.openqa.selenium.By;

import java.util.Map;
import java.util.NoSuchElementException;

import static com.atlassian.pageobjects.elements.query.Poller.waitUntilFalse;

public class PullRequestListPage extends BaseRepositoryPage {

    private final Map<String, String> filters;

    @ElementBy(id = "s2id_pr-author-filter")
    private PageElement authorFilter;

    @ElementBy(id = "list-create-pr-button")
    private PageElement createActionButton;

    @ElementBy(id = "pull-requests-content")
    private PageElement pullRequestTable;

    @ElementBy(id = "pr-reviewer-self-filter")
    private PageElement reviewingFilter;

    @ElementBy(id = "pr-state-filter")
    private SelectElement stateFilter;

    @ElementBy(id = "s2id_pr-target-branch-filter")
    private PageElement targetBranchFilter;

    public PullRequestListPage(String projectKey, String slug) {
        this(projectKey, slug, Maps.<String, String>newHashMap());
    }

    public PullRequestListPage(String projectKey, String slug, Map<String, String> filters) {
        super(projectKey, slug);

        this.filters = ImmutableMap.copyOf(filters);
    }

    public PullRequestCreatePage clickCreateActionButton() {
        createActionButton.click();
        return pageBinder.bind(PullRequestCreatePage.class, projectKey, slug);
    }

    public void filterByAuthor(String author) {
        filterByAuthor(author, author);
    }

    public void filterByAuthor(String author, String authorDisplayName) {
        filterByAsyncSelect(authorFilter, author, ".avatar-with-name[title^=\"" + escapeSelector(authorDisplayName) + "\"]",
                ".pr-author-dropdown");
    }

    public void filterByReviewerToggle() {
        waitUntilEventFired("bitbucket.internal.pull.request.list.updated",
                () -> reviewingFilter.click());
    }

    public void filterByState(PullRequestState state) {
        waitUntilEventFired("bitbucket.internal.pull.request.list.updated",
                () -> stateFilter.select(Options.value(state.name())));
    }

    public void filterByTargetBranch(String branch) {
        filterByAsyncSelect(targetBranchFilter, branch, ".name[title=\"" + escapeSelector(branch) + "\"]",
                ".pr-target-branch-dropdown");
    }

    public PullRequestTable getPullRequestTable() {
        Poller.waitUntilFalse(elementFinder.find(By.className("bb-spinner")).timed().isPresent());
        return pageBinder.bind(PullRequestTable.class, pullRequestTable);
    }

    public PullRequestTable.Entry getPullRequestTableEntry(long pullRequestId) {
        try {
            return Iterables.find(getPullRequestTable().getRows(), row -> pullRequestId == row.getId());
        } catch (NoSuchElementException e) {
            throw new IllegalStateException("No pull request with ID " + pullRequestId + " in the list");
        }
    }

    @Override
    public String getUrl() {
        return "/projects/" + projectKey + "/repos/" + slug + "/pull-requests" + getQueryString();
    }

    public boolean isBuildStatusColumnVisible() {
        PageElement buildColumn = elementFinder.find(By.cssSelector("th.build-status-pr-list-col"));
        return buildColumn.timed().isPresent().byDefaultTimeout() && buildColumn.timed().isVisible().byDefaultTimeout();
    }

    public boolean isCreateActionButtonVisible() {
        return createActionButton.timed().isPresent().byDefaultTimeout() && createActionButton.timed().isVisible().byDefaultTimeout();
    }

    public boolean isGettingStartedMessageVisible() {
        PageElement gettingStartedMessage = elementFinder.find(By.cssSelector(".pull-request-list .pull-request-intro"));
        return gettingStartedMessage.timed().isPresent().byDefaultTimeout() && gettingStartedMessage.timed().isVisible().byDefaultTimeout();
    }

    public boolean isNoResultsMessageVisible() {
        PageElement noMoreMessage = elementFinder.find(By.cssSelector(".pull-request-list .empty-banner-content"));
        return noMoreMessage.timed().isPresent().byDefaultTimeout() && noMoreMessage.timed().isVisible().byDefaultTimeout();
    }

    public boolean isNoMoreResultsMessageVisible() {
        PageElement noMoreResultsMessage = elementFinder.find(By.className("paged-table-message"));
        return noMoreResultsMessage.timed().isPresent().byDefaultTimeout() && noMoreResultsMessage.timed().isVisible().byDefaultTimeout();
    }

    private String escapeSelector(String s) {
        return s.replaceAll("\"", "\\\"");
    }

    private void filterByAsyncSelect(PageElement filter, String value, String resultSelector, String dropDownClassName) {
        PageElement choice = filter.find(By.cssSelector("a.select2-choice"));
        Poller.waitUntilTrue(choice.timed().isPresent());
        choice.click();

        // The rest of the select2 is outside of the filter container,
        // so we can't move all of the filter logic into a separate PullRequestFilterBar class.
        PageElement searchBox = elementFinder.find(By.cssSelector("#select2-drop input"));
        Poller.waitUntilTrue(searchBox.timed().isPresent());
        searchBox.click().clear().type(value);

        waitUntilFalse(elementFinder.find(By.cssSelector("#select2-drop .spinner .spinner")).timed().isPresent());
        PageElement selectDropdown = elementFinder.find(By.cssSelector("#select2-drop:not([style*='display: none'])" + dropDownClassName));
        selectDropdown.timed().isVisible();
        waitUntilEventFired("bitbucket.internal.pull.request.list.updated",
                () -> selectDropdown.find(By.cssSelector("ul.select2-results li.select2-result-selectable " + resultSelector)).click());
    }

    private String getQueryString() {
        StringBuilder buffer = new StringBuilder();
        for (Map.Entry<String, String> entry : filters.entrySet()) {
            buffer.append(buffer.length() == 0 ? "?" : "&")
                    .append(String.format("%s=%s", entry.getKey(), entry.getValue()));

        }
        return buffer.toString();
    }
}
