package com.atlassian.webdriver.bitbucket.page;

import com.atlassian.bitbucket.util.UrlUtils;
import com.atlassian.pageobjects.binder.WaitUntil;
import com.atlassian.pageobjects.elements.ElementBy;
import com.atlassian.pageobjects.elements.PageElement;
import com.atlassian.pageobjects.elements.query.Poller;
import com.atlassian.pageobjects.elements.query.Queries;
import com.atlassian.pageobjects.elements.query.TimedQuery;
import com.atlassian.pageobjects.elements.timeout.TimeoutType;
import com.atlassian.webdriver.bitbucket.element.BuildStatusDialog;
import com.atlassian.webdriver.bitbucket.element.Comment;
import com.atlassian.webdriver.bitbucket.element.GeneralCommentForm;
import com.atlassian.webdriver.bitbucket.element.Participant;
import com.atlassian.webdriver.bitbucket.element.activity.ActivityItem;
import com.atlassian.webdriver.bitbucket.element.activity.GeneralCommentActivity;
import com.atlassian.webdriver.bitbucket.element.tasks.PullRequestTaskListDialog;
import com.atlassian.webdriver.bitbucket.util.ElementUtils;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.openqa.selenium.By;
import org.openqa.selenium.Keys;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Optional;

import static com.atlassian.bitbucket.util.Chainable.chain;
import static com.google.common.base.Predicates.instanceOf;
import static com.google.common.collect.Iterables.filter;
import static java.util.Optional.empty;
import static java.util.Optional.of;

public class PullRequestOverviewPage extends PullRequestPage<PullRequestOverviewPage> {

    public static int PAGE_SIZE = 25;

    private String queryParams = "";

    @ElementBy(cssSelector = "#pull-request-activity .general-comment-form", pageElementClass = GeneralCommentForm.class)
    private GeneralCommentForm generalCommentForm;

    @ElementBy(cssSelector = ".pull-request-content .description")
    private PageElement descriptionField;

    @ElementBy(className = "pull-request-issues-trigger")
    private PageElement issuesDialogTrigger;

    @ElementBy(cssSelector = "#pull-request-header .status")
    private PageElement mergedLozenge;

    @ElementBy(cssSelector = ".watch a")
    private PageElement watchLink;

    public PullRequestOverviewPage(@Nonnull String projectKey, @Nonnull String slug,
                                   long pullRequestId, @Nullable Long commentId, @Nullable String action) {
        super(projectKey, slug, pullRequestId);
        this.queryParams = "?" + UrlUtils.buildQueryParams("commentId", commentId, "action", action);
    }

    public PullRequestOverviewPage(String projectKey, String slug, long pullRequestId) {
        super(projectKey, slug, pullRequestId);
    }

    public boolean currentUserIsWatching() {
        return (Boolean) javascriptExecutor.executeScript("return require('bitbucket/internal/model/page-state').getIsWatching();");
    }

    @Nonnull
    @SuppressWarnings("unchecked")
    public <A extends ActivityItem> TimedQuery<Iterable<A>> getActivitiesOfType(@Nonnull final Class<A> activityType) {
        // we can cast since the filter makes sure that the remaining activities are of the correct type
        return Queries.forSupplier(timeouts, () -> (Iterable) chain(getActivityItems()).filter(Predicates.instanceOf(activityType)::apply).toList());
    }

    /**
     * @return list of all activity items visible on this page
     */
    public List<ActivityItem> getActivityItems() {
        waitUntilActivityItemsLoaded();
        List<ActivityItem> elements = Lists.newLinkedList();
        List<PageElement> activitiesAndCommentForm = elementFinder.findAll(By.cssSelector("#pull-request-activity > li"));
        // Remove the comment form
        Iterable<PageElement> activities = Iterables.skip(activitiesAndCommentForm, 1);
        for (PageElement activityElement : activities) {
            elements.add(pageBinder.bind(ActivityItem.getType(activityElement), activityElement));
        }
        return elements;
    }

    /**
     * @param position position of the comment
     * @return general comment at given {@code position}
     * @throws IndexOutOfBoundsException if there is no general comment at {@code position}
     */
    public Comment getGeneralCommentAt(int position) {
        GeneralCommentActivity activity = (GeneralCommentActivity) Iterables.get(getGeneralComments(), position);
        return activity.getComment();
    }

    public GeneralCommentForm getGeneralCommentForm() {
        return generalCommentForm;
    }

    private Iterable<? extends ActivityItem> getGeneralComments() {
        return filter(getActivityItems(), instanceOf(GeneralCommentActivity.class));
    }

    public ActivityItem getLatestActivityItem() {
        PageElement activityElement = elementFinder.find(By.cssSelector(".activity-item"), TimeoutType.PAGE_LOAD);
        return pageBinder.bind(ActivityItem.getType(activityElement), activityElement);
    }

    public PageElement getMergedLozenge() {
        return mergedLozenge;
    }

    public PageElement getMergeStrategiesList() {
        return elementFinder.find(By.id("merge-strategy-opts"), TimeoutType.PAGE_LOAD);
    }

    private PageElement getOpenTaskCountPanel() {
        return elementFinder.find(By.className("plugin-item-task-count"));
    }

    public String getPullRequestDescriptionAsHtml() {
        return descriptionField.isPresent() ? ElementUtils.getHtml(descriptionField) : null;
    }

    private PullRequestTaskListDialog getPullRequestTaskListDialog() {
        return pageBinder.bind(PullRequestTaskListDialog.class);
    }

    public List<Participant> getReviewers() {
        return elementFinder.find(By.className("reviewer-avatar-list")).findAll(By.className("aui-avatar-badged"), Participant.class);
    }

    @Override
    public String getUrl() {
        return super.getUrl() + "/overview" + queryParams;
    }

    public boolean hasBuildStatus() {
        return elementFinder.find(By.className("build-icon")).isPresent();
    }

    public void loadNextPage() {
        waitUntilEventFired("bitbucket.internal.feature.pullRequestActivity.dataLoaded",
                () -> ElementUtils.scrollDocument(content));
    }

    @Nonnull
    public BuildStatusDialog openBuildStatus() {
        elementFinder.find(By.className("build-icon")).click();
        return pageBinder.bind(BuildStatusDialog.class, By.id("build-status-dialog"), TimeoutType.DIALOG_LOAD);
    }

    public MergeHelpDialog openMergeHelpDialog() {
        PageElement mergeInstructionsLink = content.find(By.cssSelector(".merge-conflicted-message .manual-merge"),
                TimeoutType.DEFAULT);
        mergeInstructionsLink.click();

        return pageBinder.bind(MergeHelpDialog.class);
    }

    public Optional<InlineMergeWarningDialog> openMergeInlineWarnings() {
        if (getMergeButton().isEnabled()) {
            return empty();
        }
        content.find(By.className("merge-button")).click();

        return of(pageBinder.bind(InlineMergeWarningDialog.class));
    }

    public PullRequestTaskListDialog openTaskListDialog() {
        PageElement panel = getOpenTaskCountPanel();
        panel.find(By.tagName("a")).click();
        return getPullRequestTaskListDialog();
    }

    public PullRequestTaskListDialog openTaskListDialogWithKeyboardShortcut() {
        elementFinder.find(By.tagName("body")).type(Keys.chord(Keys.SHIFT, "t"));
        return getPullRequestTaskListDialog();
    }

    public void unwatchPullRequest() {
        waitUntilEventFired("bitbucket.internal.web.watch-button.removed", () -> getWatchButton().click());
    }

    public int waitForOpenTaskCountPanel(boolean visible) {
        PageElement panel = getOpenTaskCountPanel();

        if (visible) {
            Poller.waitUntilTrue(panel.timed().isVisible());
            return Integer.parseInt(panel.find(By.className("task-count")).getAttribute("data-open"));
        } else {
            Poller.waitUntilFalse(panel.timed().isVisible());
            return 0;
        }
    }

    public void watchPullRequest() {
        waitUntilEventFired("bitbucket.internal.web.watch-button.added", () -> getWatchButton().click());
    }

    @WaitUntil
    public void waitUntilActivityItemsLoaded() {
        // General comments don't have the activity-item class, so we need to look for them, too
        Poller.waitUntilTrue(elementFinder.find(By.cssSelector(".activity-item,.general-comment-activity")).timed().isPresent());
    }

}
