package com.zjty.autotest.service;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Queues;
import com.google.common.collect.Sets;
import com.zjty.autotest.util.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.openqa.selenium.*;
import org.springframework.stereotype.Service;

import java.util.*;

import static com.zjty.autotest.common.action.LabelType.*;
import static java.util.Objects.nonNull;

@SuppressWarnings("FieldCanBeLocal")
@Slf4j
@Service
public class SeleniumExecutor {

    private WebDriver driver;

    private Map<String, String> inputs = Maps.newHashMap();

    private Set<String> historyUrls = Sets.newHashSet();

    private Queue<String> windowQueue = Queues.newLinkedBlockingDeque();

    private Queue<String> urlQueue = Queues.newLinkedBlockingDeque();

    private List<WebElement> elements = Lists.newArrayList();

    private Integer elementIndex;

    private String currentWindow;

    private String currentUrl;

    public List<String> execute(String url) {
        currentUrl = url;
        currentWindow = driver.getWindowHandle();
        while (nonNull(currentWindow)) {
            driver.switchTo().window(currentWindow);
            while (nonNull(currentUrl)) {
                testUrl(currentUrl);
                currentUrl = urlQueue.poll();
            }
            currentWindow = windowQueue.poll();
            driver.close();
            checkWindow();
        }
        return Lists.newArrayList(historyUrls);
    }

    private void testUrl(String currentUrl) {
        if (!historyUrls.contains(currentUrl)) {
            historyUrls.add(driver.getCurrentUrl());
            long startTime = System.currentTimeMillis();
            driver.get(currentUrl);
            long endTime = System.currentTimeMillis();
            long costTime = endTime - startTime;
            log.info("当前URL：{}  响应时间 {} ms  开始进行遍历...", currentUrl, costTime);
            log.info("正在获取当前网页所有元素...");

            getAllElements(driver);
            log.info("获取完毕...共{}个元素...", elements.size());
            elementIndex = 0;
            traversal();
            log.info("遍历完毕...");
        }
    }

    private void traversal() {
        try {
            while (elementIndex < elements.size()) {
                WebElement element = elements.get(elementIndex);
                act(element);
                checkPage();
            }
        } catch (Exception e) {
            log.error("error:" + e.getMessage());
        }
    }

    private void act(WebElement element) {
        try {
            log.info("正在操作第{}个元素 ------ text:[{}] ----- tag：[{}]", elementIndex + 1, element.getText(), element.getTagName());
            elementIndex++;
            if (isEnabledAndDisplayed(element)) {
                if (isEnabledInput(element)) {
                    String id = element.getAttribute("id");
                    String inputValue = inputs.get(id);
                    element.sendKeys(inputValue);
                    sleep(1000L);
                } else if (isEnabledClick(element)) {
                    element.click();
                }
            }
            sleep(2000L);
        } catch (StaleElementReferenceException e) {
            reload();
        } catch (ElementNotInteractableException e) {
            log.error("error:在可操作范围之外的元素" );
        } catch (Exception e) {
            log.error("error:" + e.getMessage());
        }
    }

    private void checkPage() {
        boolean pageChange = !Objects.equals(currentUrl, driver.getCurrentUrl());
        if (pageChange) {
            boolean newPage = !historyUrls.contains(driver.getCurrentUrl());
            if (newPage) {
                urlQueue.add(driver.getCurrentUrl());
            }
            reload();
        }
    }

    private void checkWindow() {
        if (!windowQueue.isEmpty()) {
            windowQueue = Queues.newLinkedBlockingQueue(driver.getWindowHandles());
        }
    }

    private void reload() {
        driver.get(currentUrl);
        getAllElements(driver);
        traversal();
    }

    private void sleep(Long time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void getAllElements(WebDriver driver) {
        elements.clear();
        driver.findElements(By.xpath("*"))
                .forEach(this::getSubElements);
    }

    private void getSubElements(WebElement element) {
        try {
            List<WebElement> results = element.findElements(By.xpath("*"));
            if (results.isEmpty()) {
                elements.add(element);
            } else {
                results.forEach(this::getSubElements);
            }
        } catch (StaleElementReferenceException e) {

        }
    }

    private Boolean isEnabledInput(WebElement element) {
        return Objects.equals(element.getTagName(), INPUT);
    }

    private Boolean isEnabledClick(WebElement element) {
        return Objects.equals(element.getTagName(), BUTTON) || Objects.equals(element.getTagName(), A);
    }

    private Boolean isEnabledAndDisplayed(WebElement element) {
        return element.isEnabled() && element.isDisplayed();
    }


    private void screenShot(String name) {
        byte[] bytes = ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
        FileUtil.output(bytes, FileUtil.WORK_PATH + name + ".png");
    }

    //
    // Setter
    //
    public void setDriver(WebDriver driver) {
        this.driver = driver;
    }

    public void setInputs(Map<String, String> inputs) {
        this.inputs = inputs;
    }

}
