package com.zjty.autotest.service.impl;

import com.google.common.collect.Lists;
import com.google.common.collect.Queues;
import com.google.common.collect.Sets;
import com.zjty.autotest.pojo.report.Measure;
import com.zjty.autotest.pojo.report.Report;
import com.zjty.autotest.pojo.test.Input;
import com.zjty.autotest.pojo.test.Project;
import com.zjty.autotest.util.FileUtil;
import com.zjty.autotest.util.WebDriverUtil;
import lombok.extern.slf4j.Slf4j;
import org.openqa.selenium.*;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.function.Predicate;

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

/**
 * @author C
 */
@SuppressWarnings("FieldCanBeLocal")
@Slf4j
@Service
public class SeleniumExecutor {

    private WebDriver driver;

    private List<Input> inputs = Lists.newArrayList();

    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;

    /**
     * 当前测试的url
     */
    private String currentUrl;

    public Report execute(Project project) {
        driver = WebDriverUtil.getWebDriver(project.getBrowser());
        inputs = project.getInputs();
        List<Measure> measures = Lists.newArrayList();
        String os = "";
        currentUrl = project.getUrl();
        currentWindow = driver.getWindowHandle();
        while (nonNull(currentWindow)) {
            driver.switchTo().window(currentWindow);
            while (nonNull(currentUrl)) {
                Measure measure = testUrl(currentUrl);
                measures.add(measure);
                currentUrl = urlQueue.poll();
            }
            currentWindow = windowQueue.poll();
            driver.close();
            checkWindow();
        }
        return new Report(
                null,
                project.getId(),
                project.getName(),
                project.getBrowser(),
                os,
                measures
        );
    }

    private Measure testUrl(String currentUrl) {
        Measure measure = null;
        //如果该网页是未曾进入过的网页则加入历史记录并进行遍历测试
        if (!historyUrls.contains(currentUrl)) {
            historyUrls.add(driver.getCurrentUrl());
            //打开网页 记录响应时间
            long startTime = System.currentTimeMillis();
            driver.get(currentUrl);
            long endTime = System.currentTimeMillis();
            Long responseTime = endTime - startTime;
            log.info("当前URL：{}  响应时间 {} ms  开始进行遍历...", currentUrl, responseTime);
            log.info("正在获取当前网页所有元素...");
            //获取当前网页的所有元素并放入元素队列
            getAllElements(driver);
            log.info("获取完毕...共{}个元素...", elements.size());
            //元素序号重置
            elementIndex = 0;
            //开始遍历操作元素队列中的元素 返回错误信息
            String message = traversal();
            //如果无错误信息则是通过
            Boolean success = Objects.equals(message, "");
            //如果出错 则提供截图
            String screenshot = "";
            if (!success) {
                String screenshotName = alterUrlName(currentUrl);
                screenshot(screenshotName);
                screenshot = String.format("http://localhost:13500/screenshots/%s", screenshotName);
            }
            log.info("遍历完毕...");
            measure = new Measure(
                    currentUrl,
                    success,
                    responseTime.intValue(),
                    message,
                    screenshot
            );
        }
        return measure;
    }

    private String traversal() {
        StringBuilder message = new StringBuilder();
        while (elementIndex < elements.size()) {
            WebElement element = elements.get(elementIndex);
            message.append(act(element));
        }
        return message.toString();
    }

    private String act(WebElement element) {
        String message = null;
        elementIndex++;
        try {
            log.info("正在操作第{}个元素 ------ text:[{}] ----- tag：[{}]", elementIndex + 1, element.getText(), element.getTagName());
            if (isEnabledAndDisplayed(element)) {
                if (isEnabledInput(element)) {
                    inputValue(element, inputs);
                } else if (isEnabledClick(element)) {
                    element.click();
                }
            }
            //一旦页面发生过跳转 通过重新打开回到原来的页面后需要重新获取元素
            Boolean pageChanged = checkPage();
            if (pageChanged) {
                reload();
            }
            //警告窗口处理
            Alert alert = getAlert();
            if (nonNull(alert)) {
                message = "出现警告窗：" + alert.getText();
                alert.accept();
            }
        } catch (ElementNotInteractableException e) {
            message = "异常的布局；";
            log.error("error:在可操作范围之外的元素");
        } catch (TimeoutException e) {
            message = "第" + (elementIndex + 1) + "个元素操作超时；";
            log.error("error:操作超时");
        } catch (Exception e) {
            message = "预料之外的异常:" + e.getMessage() + "；";
            log.error("error:" + e);
        }
        return message;
    }

    private Boolean checkPage() {
        boolean pageChange = !Objects.equals(
                getMainUrl(currentUrl),
                getMainUrl(driver.getCurrentUrl())
        );
        if (pageChange) {
            boolean newPage = !historyUrls.contains(driver.getCurrentUrl());
            if (newPage) {
                urlQueue.add(driver.getCurrentUrl());
            }
            reload();
            return true;
        } else {
            return false;
        }
    }

    private String getMainUrl(String url) {
        return url.substring(0, url.indexOf(url));
    }

    private void inputValue(WebElement element, List<Input> inputs) {
        Predicate<Input> inputMatch = input -> Objects.equals(element.getAttribute(input.getAttrName()), input.getAttrValue());
        Optional<Input> inputOptional = inputs.stream().filter(inputMatch).findAny();
        if (inputOptional.isPresent()) {
            Input input = inputOptional.get();
            element.sendKeys(input.getValue());
        }
    }

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

    private Alert getAlert() {
        WebDriverWait wait = new WebDriverWait(driver, 4);
        return wait.until((ExpectedCondition<Alert>) theDriver -> {
            try {
                return theDriver.switchTo().alert();
            } catch (NoAlertPresentException e) {
                return null;
            }
        });
    }

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

    private void sleep() {
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

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

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

    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 String alterUrlName(String fileName) {
        // /\?*:"<>|
        return fileName.replace("/", "")
                .replace("\\", "")
                .replace("?", "")
                .replace("*", "")
                .replace(":", "")
                .replace("<", "")
                .replace(">", "")
                .replace("|", "");
    }

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

}
