package com.atlassian.webdriver.bitbucket.page;

import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.test.DefaultFuncTestData;
import com.atlassian.pageobjects.DelayedBinder;
import com.atlassian.pageobjects.Page;
import com.atlassian.pageobjects.binder.PageBindingWaitException;
import com.atlassian.pageobjects.elements.ElementBy;
import com.atlassian.pageobjects.elements.PageElement;
import com.atlassian.pageobjects.elements.WebDriverCheckboxElement;
import com.atlassian.pageobjects.elements.query.Poller;
import com.atlassian.pageobjects.page.HomePage;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.http.client.utils.URIBuilder;
import org.openqa.selenium.By;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URISyntaxException;

public class BitbucketLoginPage extends BitbucketPage implements com.atlassian.pageobjects.page.LoginPage {

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

    private final boolean overrideNextParam;
    private final boolean embedded;
    private final String permission;

    @ElementBy(name = "j_password")
    private PageElement passwordField;
    @ElementBy(name = "submit")
    private PageElement submitButton;

    /**
     * The presence of a "j_username" field is used in some tests to decide whether we have correctly landed on the /login page, or
     * have been redirected to /projects (because we are already logged in).  See {@link BitbucketFailFastLoginPage#doWait()}.
     */
    @ElementBy(name = "j_username")
    private PageElement usernameField;

    @ElementBy(name = "_atl_remember_me")
    private WebDriverCheckboxElement rememberMeCheckbox;
    @ElementBy(id = "captcha")
    private PageElement captcha;
    @ElementBy(id = "captcha-image")
    private PageElement captchaImage;
    @ElementBy(id = "captcha-reload")
    private PageElement captchaReloadButton;
    @ElementBy(name = "permission")
    private PageElement permissionField;

    public BitbucketLoginPage() {
        this(true);
    }

    public BitbucketLoginPage(final boolean overrideNextParam) {
        this(overrideNextParam, false, (String) null);
    }

    public BitbucketLoginPage(final boolean overrideNextParam, boolean embedded, Permission permission) {
        this(overrideNextParam, embedded, permission.name());
    }

    public BitbucketLoginPage(final boolean overrideNextParam, boolean embedded, String permission) {
        this.overrideNextParam = overrideNextParam;
        this.embedded = embedded;
        this.permission = permission;
    }

    @Override
    public String getUrl() {
        try {
            URIBuilder uri = new URIBuilder("/login");
            if (embedded) {
                uri.addParameter("embedded", Boolean.TRUE.toString());
            }
            if (permission != null) {
                uri.addParameter("permission", permission);
            }

            return uri.build().toString();
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Return value of the hidden field 'permission'
     *
     * @return value of the 'permission' field, or {@code null}
     */
    public String getPermission() {
        return permissionField.timed().isPresent().byDefaultTimeout() ? permissionField.getValue() : null;
    }

    public String getUsername() {
        return usernameField.timed().isPresent().byDefaultTimeout() ? usernameField.getValue() : null;
    }

    public boolean isEmbedded() {
        return body.hasClass("user-login-embedded");
    }

    @Override
    public <M extends Page> M login(final String username, final String password, final Class<M> nextPage) {
        return login(username, password, false, nextPage);
    }

    public <M extends Page> M login(final String username, final String password, final Class<M> nextPage, Object... args) {
        return login(username, password, false, nextPage, args);
    }

    public BitbucketLoginPage loginFail(final String username, final String password, final boolean rememberMe) {
        Poller.waitUntilTrue(usernameField.timed().isVisible());
        Poller.waitUntilTrue(passwordField.timed().isVisible());
        usernameField.clear();
        usernameField.type(username);
        passwordField.type(password);
        setRememberMe(rememberMe);

        submitButton.click();

        BitbucketLoginPage loginPage = pageBinder.bind(BitbucketLoginPage.class);
        Poller.waitUntilTrue(loginPage.isHereTimed());
        return loginPage;
    }

    public <M extends  Page> M login(final String username, final String password, final boolean rememberMe, final Class<M> nextPage, Object... args) {
        // Try two times + plus last case
        for (int i = 0; i < 2; i++) {
            usernameField.clear();
            usernameField.type(username);
            try {
                return loginWithUserPreFilled(password, rememberMe, nextPage, args);
            } catch (PageBindingWaitException e) {
                // Ignore
                log.info("------------------------>>>> Attempt {} to login failed <<<<--------------------------", i, e);
            }
        }
        usernameField.clear();
        usernameField.type(username);
        return loginWithUserPreFilled(password, rememberMe, nextPage, args);
    }

    public <M extends  Page> M loginWithUserPreFilled(final String password, final boolean rememberMe, final Class<M> nextPage, Object... args) {
        passwordField.type(password);
        setRememberMe(rememberMe);
        final DelayedBinder<M> delayedBinder = pageBinder.delayedBind(nextPage, args);
        if (!HomePage.class.isAssignableFrom(nextPage) && overrideNextParam) {
            // Don't judge me
            javascriptExecutor.executeScript(
                "(function() {" +
                    "var $form = jQuery('form.aui')," +
                    "$next = $form.find('input[name=\"next\"]');" +
                    "if ($next.length) { $next.remove(); } else { $next = jQuery('<input name=\"next\" type=\"hidden\">'); }" +
                    "$next.val('" + StringEscapeUtils.escapeEcmaScript(delayedBinder.get().getUrl()) + "');" +
                    "$next.appendTo($form);" +
                "})()"
            );
        }
        submitButton.click();

        M page = delayedBinder.bind();
        if (page instanceof AbstractPage) {
            AbstractPage abstractPage = (AbstractPage) page;
            Poller.waitUntilTrue(abstractPage.isHereTimed());
        }
        return page;
    }

    @Override
    public <M extends Page> M loginAsSysAdmin(final Class<M> nextPage) {
        return login(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword(), nextPage);
    }

    public <M extends Page> M loginAsSysAdmin(final Class<M> nextPage, Object... args) {
        return login(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword(), nextPage, args);
    }

    public <M extends Page> M loginAsRegularUser(final Class<M> nextPage, Object... args) {
        return login(DefaultFuncTestData.getRegularUser(), DefaultFuncTestData.getRegularUserPassword(), nextPage, args);
    }

    public boolean canSignUp() {
        PageElement signUpLink = elementFinder.find(By.linkText("Need an account? Sign up."));
        return signUpLink.timed().isPresent().byDefaultTimeout() && signUpLink.timed().isVisible().byDefaultTimeout();
    }

    public SignUpPage clickSignUp() {
        PageElement signUpLink = elementFinder.find(By.linkText("Need an account? Sign up."));
        Poller.waitUntilTrue(signUpLink.timed().isPresent());
        signUpLink.click();
        SignUpPage signUpPage = pageBinder.bind(SignUpPage.class);
        Poller.waitUntilTrue(signUpPage.isHereTimed());
        return signUpPage;
    }

    public BitbucketLoginPage setCaptcha(String captcha) {
        this.captcha.clear().type(captcha);
        return this;
    }

    public String getCaptchImageSrc() {
        return captchaImage.getAttribute("src");
    }

    public void clickCaptchaReload() {
        captchaReloadButton.click();
    }

    public boolean hasSuccessfulSignUpMessage() {
        PageElement message = elementFinder.find(By.cssSelector(".aui-message-success.successful-signup"));
        return message.timed().isPresent().byDefaultTimeout() && message.timed().isVisible().byDefaultTimeout();
    }

    private void setRememberMe(boolean rememberMe) {
        // only works on non-embedded login
        if (!isEmbedded()) {
            Poller.waitUntilTrue(rememberMeCheckbox.timed().isVisible());
            if (rememberMe) {
                rememberMeCheckbox.check();
            } else {
                rememberMeCheckbox.uncheck();
            }
        }
    }
}
