package com.atlassian.webdriver.bitbucket.page;

import com.atlassian.pageobjects.binder.WaitUntil;
import com.atlassian.pageobjects.elements.ElementBy;
import com.atlassian.pageobjects.elements.PageElement;
import com.atlassian.pageobjects.elements.PageElementActions;
import com.atlassian.pageobjects.elements.query.Poller;
import com.atlassian.pageobjects.elements.timeout.TimeoutType;
import com.atlassian.webdriver.bitbucket.element.*;
import com.atlassian.webdriver.bitbucket.element.activity.MergeButton;
import com.atlassian.webdriver.bitbucket.element.activity.ToolbarButton;
import com.atlassian.webdriver.bitbucket.util.KeyboardShortcuts;
import com.google.common.base.Supplier;
import org.openqa.selenium.By;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;

import static com.google.common.base.Suppliers.ofInstance;
import static org.apache.commons.lang3.StringUtils.substringAfter;

public abstract class PullRequestPage<T extends PullRequestPage<T>> extends BaseRepositoryPage {

    private static final Logger log = LoggerFactory.getLogger(PullRequestPage.class);

    private long pullRequestId;

    @ElementBy(cssSelector = ".pull-request-content .author-avatar-item .user-avatar")
    private Avatar authorAvatar;

    @ElementBy(cssSelector = ".pull-request-content .author")
    private PageElement authorField;

    @ElementBy(cssSelector = ".pull-request-more-trigger")
    private PageElement moreButton;

    @ElementBy(cssSelector = ".content-body .aui-page-panel-content > .aui-tabs")
    private PullRequestTabs pullRequestTabs;

    @ElementBy(cssSelector = ".ref-name-from")
    private PageElement sourceBranchLozengeName;

    @ElementBy(cssSelector = ".ref-name-to")
    private PageElement targetBranchLozengeName;

    @ElementBy(cssSelector = ".pull-request-header h2")
    private PageElement titleField;

    @ElementBy(cssSelector = "#edit-pull-request-dialog")
    private PageElement editDialog;

    @Inject
    private PageElementActions actions;

    public PullRequestPage(String projectKey, String slug, long pullRequestId) {
        super(projectKey, slug);
        this.pullRequestId = pullRequestId;
    }

    public boolean canEdit() {
        return content.find(getMoreButtonSelector("edit")).isPresent();
    }

    @Override
    public void doWait() {
        if (pullRequestId == 0) {
            String url = driver.getCurrentUrl();
            try {
                pullRequestId = Long.parseLong(substringAfter(url, "/pull-requests/").split("/")[0]);
            } catch (NumberFormatException e) {
                log.warn("Error parsing pull request id in {}", url, e);
            }
        }
        super.doWait();
    }

    public void editWithShortcut() {
        KeyboardShortcuts.editPullRequest(body);
    }

    public ToolbarButton<T> getAddSelfButton() {
        return getToolbarButton("add-self .aui-button", noSpinner());
    }

    public ToolbarButton<T> getApproveButton() {
        return getToolbarButton("approve", noSpinner());
    }

    public ToolbarButton<PullRequestDeclineDialog<T>> getDeclineButton() {
        return getToolbarButtonInMore("decline", () -> pageBinder.bind(PullRequestDeclineDialog.class,
                By.id("pull-request-decline-dialog"), TimeoutType.DIALOG_LOAD, PullRequestPage.this));
    }

    public ToolbarButton<PullRequestEditDialog<T>> getEditButton() {
        return getToolbarButtonInMore("edit", () -> pageBinder.bind(PullRequestEditDialog.class,
                By.id("edit-pull-request-dialog"), TimeoutType.DIALOG_LOAD, PullRequestPage.this));
    }

    public PageElement getEditDialog() {
        return editDialog;
    }

    public String getFullSourceName() {
        return sourceBranchLozengeName.getAttribute("original-title");
    }

    public MergeButton<T> getMergeButton() {
        By locator = getToolbarButtonSelector("merge-button");
        return pageBinder.bind(MergeButton.class, locator, TimeoutType.DEFAULT, this,
                (Supplier<PullRequestMergeConfirmDialog<T>>) () -> pageBinder.bind(PullRequestMergeConfirmDialog.class,
                        By.id("pull-request-merge-dialog"), TimeoutType.DIALOG_LOAD, PullRequestPage.this));
    }

    public ToolbarButton<T> getNeedsWorkButton() {
        return getToolbarButton("aui-button.needs-work", noSpinner());
    }

    public long getPullRequestId() {
        return pullRequestId;
    }

    public String getPullRequestAuthor() {
        return authorField.getText();
    }

    public Avatar getPullRequestAuthorAvatar() {
        return authorAvatar;
    }

    public String getPullRequestTitle() {
        return titleField.getText();
    }

    public PullRequestRemoveSelfDialog getRemoveSelfDialog() {
        PageElement selfReviewerAvatar = content.find(By.cssSelector(".pull-request-actions .reviewer-avatar-list .user-avatar"));
        PageElement removeSelfButton = content.find(By.cssSelector(".remove-self .aui-button"));

        actions.moveToElement(selfReviewerAvatar).perform();
        removeSelfButton.click();

        return pageBinder.bind(PullRequestRemoveSelfDialog.class, this);
    }

    public ToolbarButton<T> getReopenButton() {
        return getToolbarButton("reopen-button", noSpinner());
    }

    public String getSourceBranch() {
        return sourceBranchLozengeName.find(By.cssSelector(".branch-name")).getText();
    }

    public String getTargetBranch() {
        return targetBranchLozengeName.find(By.cssSelector(".branch-name")).getText();
    }

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

    public ToolbarButton getWatchButton() {
        return getToolbarButtonInMore("watch", noSpinner());
    }

    public boolean isAddSelfButtonPresent() {
        return content.find(getToolbarButtonSelector("add-self .aui-button")).isPresent();
    }

    public PullRequestCommitsPage switchToCommits() {
        scrollToTop();
        pullRequestTabs.clickCommitsTab();
        PullRequestCommitsPage page = pageBinder.bind(PullRequestCommitsPage.class, projectKey, slug, pullRequestId);
        page.waitUntilCommitsTableLoaded();
        return page;
    }

    public PullRequestEffectiveDiffPage switchToEffectiveDiff() {
        scrollToTop();
        pullRequestTabs.clickDiffTab();
        return pageBinder.bind(PullRequestEffectiveDiffPage.class, projectKey, slug, pullRequestId);
    }

    public PullRequestOverviewPage switchToOverview() {
        scrollToTop();
        pullRequestTabs.clickOverviewTab();
        return pageBinder.bind(PullRequestOverviewPage.class, projectKey, slug, pullRequestId);
    }

    @WaitUntil
    public void waitUntilPageLoaded() {
        Poller.waitUntilTrue(elementFinder.find(By.className("pull-request-header")).timed().isPresent());
        Poller.waitUntilFalse(elementFinder.find(By.className("spinner")).timed().isVisible());
    }

    private By getMoreButtonSelector(String buttonDataAction) {
        return By.cssSelector("#pull-request-header-more button[data-action='" + buttonDataAction + "']");
    }

    private <X> ToolbarButton<X> getToolbarButton(String buttonClass, Supplier<X> supplier) {
        By locator = getToolbarButtonSelector(buttonClass);
        return pageBinder.bind(ToolbarButton.class, locator, TimeoutType.DEFAULT, this, supplier);
    }

    private <X> ToolbarButton<X> getToolbarButtonInMore(String buttonDataAction, final Supplier<X> supplier) {
        if (!body.find(By.cssSelector("#pull-request-header-more")).isVisible()) {
            moreButton.click();
        }
        By locator = getMoreButtonSelector(buttonDataAction);
        return pageBinder.bind(ToolbarButton.class, locator, TimeoutType.DEFAULT, this, supplier);
    }

    private By getToolbarButtonSelector(String buttonClass) {
        return By.cssSelector(".pull-request-actions ." + buttonClass);
    }

    private Supplier<T> noSpinner() {
        return ofInstance((T) this);
    }
}
