package com.zjty.automatedtesting.service.impl;

import com.google.common.collect.Lists;
import com.mysql.cj.exceptions.AssertionFailedException;
import com.zjty.automatedtesting.common.action.Browser;
import com.zjty.automatedtesting.pojo.report.Measure;
import com.zjty.automatedtesting.pojo.report.Report;
import com.zjty.automatedtesting.pojo.report.ReportVo;
import com.zjty.automatedtesting.pojo.test.Assert;
import com.zjty.automatedtesting.pojo.test.Step;
import com.zjty.automatedtesting.pojo.test.CaseVo;
import com.zjty.automatedtesting.service.ReportService;
import com.zjty.automatedtesting.service.SeleniumService;
import com.zjty.automatedtesting.util.CommonUtils;
import lombok.extern.slf4j.Slf4j;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.support.ui.Select;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.zjty.automatedtesting.common.action.Action.*;
import static com.zjty.automatedtesting.common.action.Assertion.*;
import static com.zjty.automatedtesting.common.action.ByType.*;
import static com.zjty.automatedtesting.util.JsonUtil.toJSon;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;

/**
 * @author C
 */
@SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
@Slf4j
@Service
public class SeleniumServiceImpl implements SeleniumService {


    @Autowired
    ReportService reportService;


    @Override
    public ReportVo execute(CaseVo testCase) {
        WebDriver driver = initDriver(testCase.getBrowser());
        driver.get(testCase.getUrl());
        return executeSteps(testCase.getUrl(), driver, testCase);
    }

    /**
     * 初始化driver
     *
     * @return driver
     */
    private WebDriver initDriver(String browser) {
        if (Objects.equals(browser, Browser.FIREFOX)) {
            System.setProperty("webdriver.firefox.driver", CommonUtils.FIRE_FOX_EXE);
            return new FirefoxDriver();
        } else if (Objects.equals(browser, Browser.CHROME)) {
            System.setProperty("webdriver.chrome.driver", CommonUtils.CHROME_EXE);
            return new ChromeDriver();
        } else if (Objects.equals(browser, Browser.IE)) {
            System.setProperty("webdriver.ie.driver", CommonUtils.IE_EXE);
            return new InternetExplorerDriver();
        } else {
            throw new RuntimeException("该浏览器不存在：" + browser);
        }
    }

    private ReportVo executeSteps(String url, WebDriver driver, CaseVo testCase) {
        List<Measure> measures = Lists.newArrayList();
        List<Step> steps = testCase.getSteps().stream().sorted(Comparator.comparingInt(Step::getOrder)).collect(Collectors.toList());
        for (Step step : steps) {
            boolean success;
            String message = "通过测试";
            WebElement webElement;
            log.info("执行步骤:{}", step.getTitle());
            try {
                if (Objects.equals(step.getAction(), HOME)) {
                    driver.get(url);
                } else {
                    webElement = getWebElement(step.getType(), step.getKey(), driver);
                    if (Objects.equals(step.getAction(), INPUT)) {
                        String value = isNull(step.getValue()) ? "" : step.getValue();
                        webElement.sendKeys(value);
                    } else if (Objects.equals(step.getAction(), CLICK)) {
                        webElement.click();
                    } else if (Objects.equals(step.getAction(), SWITCH)) {
                        driver.switchTo().frame(webElement);
                    } else if (Objects.equals(step.getAction(), CLEAR)) {
                        webElement.clear();
                    } else if (Objects.equals(step.getAction(), SELECT)) {
                        Select select = new Select(webElement);
                        select.selectByIndex(Integer.parseInt(step.getValue()));
                    } else {
                        throw new WebDriverException("不匹配的操作类型：" + step.getAction());
                    }
                }
                success = assertions(step.getAsserts(), driver);
                waitTime(2000L);
            } catch (Exception e) {
                success = false;
                message = String.format("出现错误:[%s]", e.getMessage());
                log.error("出现错误 : " + e.getMessage());
            }
            measures.add(new Measure(
                    step.getOrder(),
                    step.getTitle(),
                    success,
                    message
            ));
        }
        ReportVo reportVo = new ReportVo(
                null,
                testCase.getTitle(),
                testCase.getBrowser(),
                testCase.getUrl(),
                measures
        );
        Report report = new Report(
                null,
                testCase.getId(),
                testCase.getTitle(),
                testCase.getBrowser(),
                testCase.getUrl(),
                toJSon(measures)
        );
        Integer id = reportService.save(report);
        reportVo.setId(id);
        waitTime(4000L);
        driver.quit();
        return reportVo;
    }

    private Boolean assertions(List<Assert> asserts, WebDriver driver) {
        String practice = "";
        for (Assert assertion : asserts) {
            WebElement element = getWebElement(assertion.getLocateType(), assertion.getLocateKey(), driver);
            boolean hasKey = nonNull(assertion.getAssertKey()) && !Objects.equals(assertion.getAssertKey(), "");
            if (Objects.equals(assertion.getAssertType(), EQUALS)) {
                if (hasKey) {
                    practice = element.getAttribute(assertion.getAssertKey());
                }
                if (Objects.equals(assertion.getAssertValue(), practice)) {
                    return true;
                } else {
                    throw new AssertionFailedException("");
                }
            } else if (Objects.equals(assertion.getAssertType(), NOT_EQUALS)) {
                if (hasKey) {
                    practice = element.getAttribute(assertion.getAssertKey());
                }
                if (!Objects.equals(assertion.getAssertValue(), practice)) {
                    return true;
                } else {
                    throw new AssertionFailedException("");
                }
            } else if (Objects.equals(assertion.getAssertType(), EXIST)) {
                if (nonNull(element)) {
                    return true;
                } else {
                    throw new AssertionFailedException("");
                }
            } else if (Objects.equals(assertion.getAssertType(), NOT_EXIST)) {
                if (isNull(element)) {
                    return true;
                } else {
                    throw new AssertionFailedException("");
                }
            }
        }
        return true;
    }

    @SuppressWarnings("DuplicatedCode")
    private WebElement getWebElement(String getMethod, String key, WebDriver driver) {
        WebElement webElement = null;
        driver.manage().timeouts().implicitlyWait(6, TimeUnit.SECONDS);
        if (Objects.equals(getMethod, XPATH)) {
            webElement = driver.findElement(By.xpath(key));
        } else if (Objects.equals(getMethod, CSS)) {
            webElement = driver.findElement(By.cssSelector(key));
        } else if (Objects.equals(getMethod, ID)) {
            webElement = driver.findElement(By.id(key));
        } else if (Objects.equals(getMethod, NAME)) {
            webElement = driver.findElement(By.name(key));
        } else if (Objects.equals(getMethod, LINK_TEXT)) {
            webElement = driver.findElement(By.linkText(key));
        }

        if (Objects.nonNull(webElement)) {
            return webElement;
        } else {
            throw new ElementNotVisibleException("未找到元素  定位方式：" + getMethod + "   关键值: " + key);
        }

    }

    private void waitTime(Long time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            log.error("暂停等待时出现异常:" + e);
        }
    }

}
