package com.atlassian.webdriver.bitbucket.page.repository.sync;

import com.atlassian.bitbucket.util.Chainable;
import com.atlassian.pageobjects.PageBinder;
import com.atlassian.pageobjects.elements.CheckboxElement;
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.TimedCondition;
import com.atlassian.webdriver.bitbucket.page.BitbucketPage;
import com.atlassian.webdriver.bitbucket.util.DateUtils;
import com.atlassian.webdriver.utils.by.ByDataAttribute;
import org.openqa.selenium.By;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import java.time.Instant;
import java.util.Collections;
import java.util.List;

/**
 * Describes the settings page for configuring ref sync. This page is only {@link #isAvailable() available} for forked
 * repositories. A {@link #getNotAvailableMessage() message} will be displayed when viewed from a top-level repository.
 * <p>
 * When synchronization is first {@link #setEnabled(boolean) enabled} an initial synchronization will be performed and
 * the dialog will be updated with its outcome. Once synchronization has been performed at least once:
 * <ul>
 *     <li>The {@link #getLastSync() last sync timestamp} will <i>always</i> be shown as long as synchronization
 *     remains enabled</li>
 *     <li>One or more {@link #getDivergedBranches() diverged branches} may be shown. If all branches synchronize
 *     without issue, that table will contain no rows</li>
 * </ul>
 */
public class RefSyncSettingsPage extends BitbucketPage {

    private final String projectKey;
    private final String repositorySlug;

    @ElementBy(id = "enable-ref-syncing")
    private CheckboxElement enableRefSyncCheckbox;

    @ElementBy(className = "not-available")
    private PageElement notAvailable;

    @ElementBy(className = "sync-status")
    private PageElement syncStatus;

    public RefSyncSettingsPage(String projectKey, String repositorySlug) {
        this.projectKey = projectKey;
        this.repositorySlug = repositorySlug;
    }

    @Nonnull
    public RefSyncBanner getBanner() {
        return pageBinder.bind(RefSyncBanner.class);
    }

    @Nonnull
    public Iterable<DivergedBranch> getDivergedBranches() {
        PageElement divergedRefs = syncStatus.find(By.className("diverged-refs"));
        if (divergedRefs.timed().isPresent().byDefaultTimeout()) {
            List<PageElement> rows = divergedRefs.findAll(By.tagName("tr"));

            return Chainable.chain(rows)
                    .transform(element -> pageBinder.bind(DivergedBranch.class, element))
                    .toList();
        }

        return Collections.emptyList();
    }

    @Nullable
    public Instant getLastSync() {
        PageElement lastSync = syncStatus.find(By.className("last-sync"));
        if (lastSync.timed().isPresent().byDefaultTimeout()) {
            String time = lastSync.find(By.tagName("time")).getAttribute("datetime");
            return DateUtils.parseServerTimestamp(time).toInstant();
        }

        return null;
    }

    @Nullable
    public String getNotAvailableMessage() {
        return notAvailable.timed().isPresent().byDefaultTimeout() ? notAvailable.getText() : null;
    }

    @Nonnull
    @Override
    public String getUrl() {
        return String.format("/plugins/servlet/sync/%1$s/%2$s", projectKey, repositorySlug);
    }

    public boolean hasStatus() {
        return syncStatusIsPresent().byDefaultTimeout();
    }

    public boolean isAvailable() {
        return !notAvailable.timed().isPresent().byDefaultTimeout();
    }

    public boolean isEnabled() {
        return enableRefSyncCheckbox.timed().isSelected().byDefaultTimeout();
    }

    public boolean hasEnableCheckbox() {
        return enableRefSyncCheckbox.timed().isPresent().byDefaultTimeout();
    }

    public RefSyncSettingsPage setEnabled(boolean enabled) {
        if (enabled) {
            enableRefSyncCheckbox.check();
            Poller.waitUntilTrue(syncStatusIsPresent());
        } else if (isEnabled()) {
            enableRefSyncCheckbox.uncheck();
            Poller.waitUntilFalse(syncStatusIsPresent());
        }

        return this;
    }

    private TimedCondition syncStatusIsPresent() {
        return syncStatus.timed().isPresent();
    }

    public static class DivergedBranch {

        private final PageElement row;

        @Inject
        private PageBinder pageBinder;

        public DivergedBranch(PageElement row) {
            this.row = row;
        }

        @Nonnull
        public String getName() {
            return row.find(ByDataAttribute.byData("column", "ref-name"))
                    .find(By.cssSelector(".ref .name"))
                    .timed()
                    .getText()
                    .byDefaultTimeout();
        }

        @Nonnull
        public RefSyncDialog synchronize() {
            row.find(By.className("synchronize-ref")).click();

            RefSyncDialog dialog = pageBinder.bind(RefSyncDialog.class);
            Poller.waitUntilTrue(dialog.isOpen());

            return dialog;
        }
    }
}
