/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.falcon.regression.ui.search;

import org.apache.falcon.regression.core.enumsAndConstants.MerlinConstants;
import org.apache.falcon.regression.core.util.AssertUtil;
import org.apache.falcon.regression.core.util.TimeUtil;
import org.apache.falcon.regression.core.util.UIAssert;
import org.apache.log4j.Logger;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.FindBys;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.Assert;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/** Page object for header of the search ui pages. */
public class PageHeader {
    private static final Logger LOGGER = Logger.getLogger(PageHeader.class);

    protected WebDriver driver;

    public PageHeader(WebDriver driver) {
        this.driver = driver;
    }

    @FindBy(className = "navbar")
    private WebElement header;

    @FindBys({
        @FindBy(className = "navbar"),
        @FindBy(className = "logoTitle")
    })
    private WebElement homeButton;

    @FindBys({
        @FindBy(className = "navbar"),
        @FindBy(className = "logoTitle"),
        @FindBy(className = "falconLogo")
    })
    private WebElement falconLogo;

    @FindBys({
        @FindBy(className = "navbar"),
        @FindBy(className = "loginHeaderBox")
    })
    private WebElement loginHeaderBox;

    @FindBys({
        @FindBy(className = "navbar"),
        @FindBy(className = "createNavWrapper")
    })
    private WebElement createEntityBox;

    @FindBy(id = "cluster.create")
    private WebElement clusterCreateButton;

    @FindBy(id = "feed.create")
    private WebElement feedCreateButton;

    @FindBy(id = "process.create")
    private WebElement processCreateButton;

    @FindBy(id = "dataset.create")
    private WebElement mirrorCreateButton;

    @FindBys({
        @FindBy(className = "uploadNavWrapper"),
    })
    private WebElement uploadEntityBox;

    @FindBys({
        @FindBy(className = "uploadNavWrapper"),
        @FindBy(className = "btn-file")
    })
    private WebElement uploadEntityButton;


    public WebElement getHomeButton() {
        return homeButton;
    }

    public void checkLoggedIn() {
        Assert.assertEquals(getLogoutButton().getText(), "Logout",
            "Unexpected text on logout button");
    }

    public SearchPage gotoHome() {
        homeButton.click();
        final SearchPage searchPage = PageFactory.initElements(driver, SearchPage.class);
        searchPage.checkPage();
        final PageHeader searchHeader = searchPage.getPageHeader();
        if (!MerlinConstants.IS_SECURE) {
            searchHeader.checkLoggedIn();
            Assert.assertEquals(searchHeader.getLoggedInUser(), LoginPage.UI_DEFAULT_USER,
                "Unexpected user is displayed");
        }
        return searchPage;
    }

    public void checkLoggedOut() {
        UIAssert.assertNotDisplayed(getLogoutButton(), "logout button");
    }

    /**
     * Check header and make sure all the buttons/links are working correctly. Handles both
     * logged in and logged out scenarios.
     */
    public void checkHeader() {
        //home button is always displayed
        UIAssert.assertDisplayed(homeButton, "falcon logo");
        Assert.assertEquals(homeButton.getText(), "Falcon", "Unexpected home button text");
        UIAssert.assertDisplayed(falconLogo, "falcon logo");
        final WebElement helpLink = loginHeaderBox.findElement(By.tagName("a"));
        UIAssert.assertDisplayed(helpLink, "help link");

        final String oldUrl = driver.getCurrentUrl();
        //displayed if user is logged in: create entity buttons, upload entity button, username
        if (MerlinConstants.IS_SECURE || getLogoutButton().isDisplayed()) {
            //checking create entity box
            UIAssert.assertDisplayed(createEntityBox, "Create entity box");
            final WebElement createEntityLabel = createEntityBox.findElement(By.tagName("h4"));
            Assert.assertEquals(createEntityLabel.getText(), "Create an entity",
                "Unexpected create entity text");
            //checking upload entity part
            UIAssert.assertDisplayed(uploadEntityBox, "Create entity box");
            final WebElement uploadEntityLabel = uploadEntityBox.findElement(By.tagName("h4"));
            Assert.assertEquals(uploadEntityLabel.getText(), "Upload an entity",
                "Unexpected upload entity text");
            Assert.assertEquals(uploadEntityButton.getText(), "Browse for the XML file",
                "Unexpected text on upload entity button");
            //checking if logged-in username is displayed
            if (!MerlinConstants.IS_SECURE) {
                UIAssert.assertDisplayed(getLogoutButton(), "Logout button");
                AssertUtil.assertNotEmpty(getLoggedInUser(), "Expecting logged-in username.");
            }

            //create button navigation
            doCreateCluster();
            driver.get(oldUrl);
            doCreateFeed();
            driver.get(oldUrl);
            doCreateProcess();
            driver.get(oldUrl);
            doCreateMirror();
            driver.get(oldUrl);
        }
        //home button navigation
        homeButton.click();
        Assert.assertTrue(getHomeUrls().contains(driver.getCurrentUrl()),
            "home button navigate to: " + driver.getCurrentUrl() + " instead of: " + getHomeUrls());
        driver.get(oldUrl);

        //help link navigation
        Assert.assertEquals(helpLink.getText(), "Help", "Help link expected to have text 'Help'");
        clickLink(helpLink);
        int helpPageloadTimeoutThreshold = 30;
        new WebDriverWait(driver, helpPageloadTimeoutThreshold).until(
            ExpectedConditions.stalenessOf(helpLink));
        Assert.assertEquals(driver.getCurrentUrl(), MerlinConstants.HELP_URL,
            "Unexpected help url");
        driver.get(oldUrl);
    }

    /**
     * Useful in cases when selenium fails to click a link due to it's bugs.
     */
    private void clickLink(WebElement link) {
        JavascriptExecutor executor = (JavascriptExecutor) driver;
        executor.executeScript("arguments[0].click();", link);
    }

    public void uploadXml(String filePath) throws IOException {
        final WebElement uploadEntityTextBox = uploadEntityBox.findElement(By.id("files"));
        uploadEntityTextBox.sendKeys(filePath);
        //wait for alert
        driver.findElements(
            By.xpath("//div[@class='messages notifs' and contains(@style,'opacity') and not(contains(@style,'1;'))]"));
    }

    public ClusterWizardPage doCreateCluster() {
        UIAssert.assertDisplayed(clusterCreateButton, "Cluster create button");
        Assert.assertEquals(clusterCreateButton.getText(), "Cluster",
            "Unexpected text on create cluster button");
        clusterCreateButton.click();
        final ClusterWizardPage clusterPage = PageFactory.initElements(driver, ClusterWizardPage.class);
        clusterPage.checkPage();
        return clusterPage;
    }

    public FeedWizardPage doCreateFeed() {
        UIAssert.assertDisplayed(feedCreateButton, "Feed create button");
        Assert.assertEquals(feedCreateButton.getText(), "Feed",
            "Unexpected text on create feed button");
        feedCreateButton.click();
        final FeedWizardPage feedPage = PageFactory.initElements(driver, FeedWizardPage.class);
        feedPage.checkPage();
        return feedPage;
    }

    public ProcessWizardPage doCreateProcess() {
        UIAssert.assertDisplayed(processCreateButton, "Process create button");
        Assert.assertEquals(processCreateButton.getText(), "Process",
            "Unexpected text on create process button");
        processCreateButton.click();
        final ProcessWizardPage processPage = PageFactory.initElements(driver, ProcessWizardPage.class);
        processPage.checkPage();
        return processPage;
    }

    public MirrorWizardPage doCreateMirror() {
        UIAssert.assertDisplayed(mirrorCreateButton, "Mirror create button");
        Assert.assertEquals(mirrorCreateButton.getText(), "Mirror",
            "Unexpected text on create mirror button");
        mirrorCreateButton.click();
        final MirrorWizardPage mirrorPage = PageFactory.initElements(driver, MirrorWizardPage.class);
        mirrorPage.checkPage();
        return mirrorPage;
    }

    private List<String> getHomeUrls() {
        List<String> urls = new ArrayList<>();
        String homeUrl = MerlinConstants.PRISM_URL;
        urls.add(homeUrl);
        urls.add(homeUrl.replaceAll("/$", "") + "/#/");
        return urls;
    }

    public String getLoggedInUser() {
        return loginHeaderBox.findElement(By.tagName("div")).getText();
    }

    private WebElement getLogoutButton() {
        return loginHeaderBox.findElements(By.xpath("button[@ng-click='logOut()']")).get(0);
    }

    private WebElement getNotificationButton() {
        return loginHeaderBox.findElements(By.xpath("button[@ng-click='notify()']")).get(0);
    }

    /**
     * Validates number of notifications contained by notification bar and last notification message.
     */
    public void validateNotificationCountAndCheckLast(int count, String message) {
        WebElement notificationButton = getNotificationButton();
        notificationButton.click();
        waitForAngularToFinish();

        // Test notifications dropdown visibility
        WebElement notificationDropdown = notificationButton.findElement(By.className("messages"));
        Assert.assertTrue(notificationDropdown.getAttribute("style").contains("display: block;"),
            "Notifications are not visible.");

        // Test validity of number of notifications
        List<WebElement> notifications = notificationDropdown.findElements(By.xpath("div"));
        Assert.assertEquals(notifications.size() - 1, count, "Invalid notification count.");

        // Test validity of last notification
        String lastNotification = notifications.get(0).getText();
        Assert.assertTrue(lastNotification.contains(message), "Invalid last notification text.");
    }

    public LoginPage doLogout() {
        LOGGER.info("Going to logout.");
        getLogoutButton().click();
        final LoginPage loginPage = PageFactory.initElements(driver, LoginPage.class);
        loginPage.checkPage();
        return loginPage;
    }

    protected void waitForAngularToFinish() {
        final String javaScript = "return (window.angular != null) && "
            + "(angular.element(document).injector() != null) && "
            + "(angular.element(document).injector().get('$http').pendingRequests.length === 0)";
        boolean isLoaded = false;
        for (int i = 0; i < AbstractSearchPage.PAGELOAD_TIMEOUT_THRESHOLD && !isLoaded; i++) {
            final Object output = ((JavascriptExecutor) driver).executeScript(javaScript);
            isLoaded = Boolean.valueOf(output.toString());
            LOGGER.info(i+1 + ". waiting on angular to finish.");
            TimeUtil.sleepSeconds(1);
        }
        LOGGER.info("angular is done continuing...");
    }


}
