package net.northking.cctp.se.util;

import com.alibaba.fastjson.JSON;
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.TouchAction;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.touch.WaitOptions;
import io.appium.java_client.touch.offset.PointOption;
import net.northking.cctp.element.core.exception.EnvironmentException;
import net.northking.cctp.element.core.exception.ExecuteException;
import net.northking.cctp.element.core.exception.ParamMistakeException;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * 元素操作
 */
public class ElementUtil {


    private static final Logger logger = LoggerFactory.getLogger(ElementUtil.class);

    private static final ExecutorService executorService = Executors.newFixedThreadPool(20);

    private static final String sendKeysUrl = "http://127.0.0.1:{wdaPort}/wda/cctpInput";

    private static final String windowsUrl = "http://127.0.0.1:{wdaPort}/window/size";

    /**
     * 查找控件
     * @param driver  设备驱动
     * @param controlTargets  控件的属性
     * @param timeout  超时
     * @param waitTimeout  默认超时时间
     * @param more   是否支持查询到多个
     * @param address  选择第几个
     * @param waitStatus  是否等待界面稳定
     * @param swipe  是否滑动
     * @param swipeCount  滑动次数
     * @return
     */
    public static WebElement findElement(AppiumDriver<WebElement> driver,
                                         List<ControlTarget> controlTargets,
                                         Integer timeout,
                                         Integer waitTimeout,
                                         boolean more,
                                         Integer address,
                                         boolean waitStatus,
                                         String swipe,
                                         Integer swipeCount){
        if (null == driver) {
            logger.error("driver获取失败");
            throw new EnvironmentException("设备自动化驱动连接已断开，请检查驱动程序是否正常");
        }
        //是否等待界面稳定
        WebElement webElement = null;
        if (timeout > 120 || timeout <= 0) {
            timeout = waitTimeout;
        }
        List<WebElement> webElements = findElementsByXpath(driver,controlTargets,swipe,swipeCount,timeout);
        if (webElements == null) {
            return null;
        }
        if (webElements.size() == 1) {//匹配到单个元素
            webElement = webElements.get(0);
        } else if (webElements.size() > 1) {//匹配到多个元素
            if (more) {
                if (address <= webElements.size()) {
                    webElement = webElements.get(address - 1);
                } else {
                    return null;
                }
            } else {
                return null;
            }
        }

        if (null != webElement) {
            logger.debug("查找到的元素名字是：{}", webElement.getText());
        }
        return webElement;
    }
    /**
     * 查找控件，
     * ①当有必选时，必须满足必选，找到多个才满足可选
     * ②当无必选时，可选只需要满足一个即可
     * ③忽略不需要考虑进去
     * ④priority  0-忽略,1-必选,2-可选
     * @param driver
     * @param controlTargets
     * @param swipe
     * @param swipeCount
     * @param timeout
     * @return
     */
    public static List<WebElement>  findElementsByXpath(AppiumDriver<WebElement> driver, List<ControlTarget> controlTargets, String swipe,Integer swipeCount,Integer timeout){
        //转换查询条件为xpath
        String[] xpath = targetToXpath(driver,controlTargets);
        //开始查找控件
        List<WebElement> webElements = findElements(driver,xpath,swipe,swipeCount,timeout);
        return webElements;
    }

    private static String[] targetToXpath(AppiumDriver<WebElement> driver, List<ControlTarget> controlTargets) {
        String text = "@text=";
        String id = "@resource-id=";
        String elementClass = "@class=";
        if (driver instanceof IOSDriver) {
            text = "@label=";
            id = "@name=";
            elementClass = "@type=";
        }
        int flag = 0;
        List<String> concatList = new ArrayList<>();
        List<String> selectList = new ArrayList<>();
        String[] xpath = new String[3];   //xpath数组  0-必选 xpath 1-拼接必选 xpath 2-可选 xpath
        for (ControlTarget controlTarget : controlTargets) {
            if ("0".equals(controlTarget.getStrategy())) {  //忽略
                flag++;
            } else if ("1".equals(controlTarget.getStrategy())) {  //必选
                if ("xpath".equals(controlTarget.getUsing())) {
                    xpath[0] = controlTarget.getValue();
                } else if ("id".equals(controlTarget.getUsing())) {
                    concatList.add(id + "'" + controlTarget.getValue() + "'");
                } else if ("class".equals(controlTarget.getUsing())) {
                    concatList.add(elementClass + "'" + (driver instanceof IOSDriver?"XCUIElementType" + controlTarget.getValue():controlTarget.getValue())+ "'");
                } else {
                    concatList.add(text + "'" + controlTarget.getValue() + "'");
                }
            } else {   //可选
                if ("xpath".equals(controlTarget.getUsing())) {
                    xpath[2] = controlTarget.getValue();
                } else if ("id".equals(controlTarget.getUsing())) {
                    selectList.add("//*[" + id + "'" + controlTarget.getValue() + "']");
                } else if ("class".equals(controlTarget.getUsing())) {
                    selectList.add("//*[" + elementClass + "'" + controlTarget.getValue() + "']");
                } else {
                    selectList.add("//*[" + text + "'" + controlTarget.getValue() + "']");
                }
            }
        }
        if (flag >= 4) {
            throw new ParamMistakeException("查找控件的策略都为忽略，无法精准定位到控件");
        }
        if (!CollectionUtils.isEmpty(concatList)) {   //必选
            xpath[1] = "//*[" + concatList.stream().collect(Collectors.joining(" and ")) + "]";
        }
        if (!CollectionUtils.isEmpty(selectList)) {   //可选
            if (StringUtils.hasText(xpath[2])) {
                xpath[2] = xpath[2] + "|";
            }
            xpath[2] = (null != xpath[2]?xpath[2]:"") + selectList.stream().collect(Collectors.joining(" | "));
        }
        logger.info("实际 xpath:{}",xpath[0]);
        logger.info("连接 xpath:{}",xpath[1]);
        logger.info("可选 xpath:{}",xpath[2]);
        return xpath;
    }

    private static List<WebElement> findElements(AppiumDriver<WebElement> driver, String[] xpath, String swipe, Integer swipeCount, Integer timeout) {
        int stepTimeout = timeout;
        //每屏查找时间
        if (!"0".equals(swipe)) {
            stepTimeout = timeout / (swipeCount+1);
        }
        //开始查找
        int stepTimeoutFinal = stepTimeout;
        Future<List<WebElement>> futureTask = executorService.submit(() -> {
            int num = 1;
            List<WebElement> elements = null;
            do {
                driver.manage().timeouts().implicitlyWait(stepTimeoutFinal, TimeUnit.SECONDS);
                long startTime = System.currentTimeMillis();
                if (StringUtils.hasText(xpath[0])) {   //xpath必选
                    logger.info("xpath是必选，xpath:{}", xpath[0]);
                    elements = driver.findElementsByXPath(xpath[0]);
                    if (!CollectionUtils.isEmpty(elements)) {  //必选xpath有找到元素
                        logger.info("查找到{}个控件", elements.size());
                        Long spendTime = System.currentTimeMillis() - startTime;  //必选xpath查找元素耗时
                        driver.manage().timeouts().implicitlyWait(stepTimeoutFinal * 1000 - spendTime, TimeUnit.MILLISECONDS);  //设置剩下的超时时间
                        if (StringUtils.hasText(xpath[1])) {  //其他的条件也存在必选
                            logger.info("其他也存在必选的条件,组装成xpath：{}", xpath[1]);
                            List<WebElement> concatElements = driver.findElementsByXPath(xpath[1]);
                            if (!CollectionUtils.isEmpty(concatElements)) {    //其他的必选条件查找到控件，合并
                                logger.info("其他必选条件查到{}个元素", concatElements.size());
                                elements = mergeWebElement(elements, concatElements);
                                logger.info("合并完成之后一共{}个元素", elements.size());
                                //合并之后元素不为空
                                Long spendTime2 = System.currentTimeMillis() - startTime;
                                if (!CollectionUtils.isEmpty(elements)) {
                                    if (StringUtils.hasText(xpath[2])) {  //有可选,再继续查可选
                                        driver.manage().timeouts().implicitlyWait(stepTimeoutFinal * 1000 - spendTime2 / 1000, TimeUnit.MILLISECONDS);
                                        List<WebElement> selectElements = driver.findElementsByXPath(xpath[2]);
                                        if (!CollectionUtils.isEmpty(selectElements)) {  //找到了就合并
                                            List<WebElement> mergeElements = mergeWebElement(elements, selectElements);
                                            if (null != mergeElements) {  //合并完成，不是空，就返回
                                                elements = mergeElements;
                                            }
                                        }
                                    }
                                } else {  //为空没必要再查下去，把时间睡下去
                                    Thread.sleep(stepTimeoutFinal * 1000 - spendTime2);
                                }
                            } else {  //其他必选条件没有查找到元素
                                logger.info("其他必选条件没有查找到元素");
                                elements = null;
                                logger.debug("总共查找耗时:{}s", (System.currentTimeMillis() - startTime) / 1000);
                            }
                        } else {   //不存在其他必选条件,看看是否有可选条件
                            if (StringUtils.hasText(xpath[2])) {  //有可选条件
                                List<WebElement> selectElements = driver.findElementsByXPath(xpath[2]);
                                if (!CollectionUtils.isEmpty(selectElements)) {
                                    List<WebElement> mergeWebElements = mergeWebElement(elements, selectElements);
                                    if (!CollectionUtils.isEmpty(mergeWebElements)) {
                                        elements = mergeWebElements;
                                    }
                                }
                            }
                        }
                    } else {   //xpath必选没有找到元素
                        logger.debug("xpath必选没有找到元素，查找时间 :{}s", (System.currentTimeMillis() - startTime) / 1000);
                    }
                } else {  //xpath不是必选
                    if (StringUtils.hasText(xpath[1])) {  //其他条件存在必选
                        long startTimeFind = System.currentTimeMillis();
                        elements = driver.findElementsByXPath(xpath[1]);
                        logger.info("找控件所需时间：{}ms",System.currentTimeMillis() - startTimeFind);
                        if (!CollectionUtils.isEmpty(elements)) {  //其他必选条件有找到
                            logger.info("其他必选条件找到{}个元素", elements.size());
                            if (StringUtils.hasText(xpath[2])) {
                                logger.info("有可选条件，xpath：{}", xpath[2]);
                                long spendTime = System.currentTimeMillis() - startTime;
                                driver.manage().timeouts().implicitlyWait(stepTimeoutFinal * 1000 - spendTime, TimeUnit.MILLISECONDS);
                                List<WebElement> selectElements = driver.findElementsByXPath(xpath[2]);
                                if (!CollectionUtils.isEmpty(selectElements)) {
                                    List<WebElement> mergeElement = mergeWebElement(elements, selectElements);
                                    if (!CollectionUtils.isEmpty(mergeElement)) {
                                        elements = mergeElement;
                                    }
                                }
                            }
                        } else {
                            logger.info("其他得必选条件没找到元素，共计耗时：{}", (System.currentTimeMillis() - startTime) / 1000);
                        }
                    } else {  //其他条件也不是必选，那么全是可选
                        logger.info("没有必选条件，全是可选条件，xpath：{}", xpath[2]);
                        elements = driver.findElementsByXPath(xpath[2]);
                    }
                }

                if (!CollectionUtils.isEmpty(elements)) {
                    logger.info("最终符合条件的元素有{}个", elements.size());
                    for (int i = 0; i < elements.size(); i++) {
                        WebElement element = elements.get(i);
                        logger.info("第{}个元素名字：{},位置x:{},y:{}", i + 1, element.getText(), element.getRect().getX(), element.getRect().getY());
                    }
                    return elements;
                }
                if ("0".equals(swipe)) {  //不滑屏，直接返回找不到
                    logger.warn("不滑屏，没找到控件");
                    return null;
                }
                num++;
            } while (num <= swipeCount);
            logger.warn("找了一圈都没找到元素");
            return null;
        });
        try {
            List<WebElement> webElements = futureTask.get(500, TimeUnit.SECONDS);
            return webElements;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            logger.error("查找元素中断:",e);
        } catch (ExecutionException e) {
            logger.error("查找元素失败:",e);
        } catch (TimeoutException e) {
            logger.error("查找元素超时:",e);
            futureTask.cancel(true);
        }
        return null;
    }

    private static List<WebElement> mergeWebElement(List<WebElement> elements, List<WebElement> concatElements) {
        List<WebElement> webElements = new ArrayList<>();
        for (WebElement element : elements) {
            for (WebElement concatElement : concatElements) {
                long start = System.currentTimeMillis();
                if (element.getLocation().getX() == concatElement.getLocation().getX()
                        && element.getLocation().getY() == concatElement.getLocation().getY()
                ) {
                    webElements.add(element);
                    break;
                }
                logger.info("对比一个控件耗时:{}ms",System.currentTimeMillis() - start);
            }
        }
        return webElements;
    }


    /**
     * <p>查找页面上存在的文本</p>
     * @param driver  driver链接
     * @param text   查找的文本
     * @param type  类型0-等于，1-包含contain
     * @return  查到包含文本的所有控件
     */
    public static List<WebElement> findElementsByText(AppiumDriver<WebElement> driver,String text,String type){
        if (null == driver) {
            logger.error("driver获取为空");
            throw new EnvironmentException("设备自动化驱动连接已断开，请检查驱动程序是否正常");
        }
        String s = "";
        if (driver instanceof AndroidDriver) {
            if ("0".equals(type)) {
                s = "//*[@text = '" + text + "']";
            } else {
                s = "//*[@contain = '" + text + "']";
            }
        } else if (driver instanceof IOSDriver) {
            s = "//*[@label = '" + text + "']";
        }
        logger.debug("xpath匹配:{}.", s);
        List<WebElement> elements = null;
        try {
            elements = driver.findElements(By.xpath(s));
        } catch (Exception e) {
            logger.error("查找文本控件出错", e);
            throw new ExecuteException("查找文本控件出错");
        }
        return null;
    }




    public static void elementSwipe(WebElement element,String direction,  AppiumDriver<WebElement> driver, Integer size) {
        int width = driver.manage().window().getSize().width;
        int height = driver.manage().window().getSize().height;
        int x1=element.getLocation().x;
        int y1=element.getLocation().y;
        try {
            if (driver instanceof IOSDriver ){
                String rect=element.getAttribute("rect");
                PointMessage pointMessage= JSON.parseObject(rect,PointMessage.class);
                x1=pointMessage.getX()+ pointMessage.getWidth()/2;
                y1=pointMessage.getY()+ pointMessage.getHeight()/2;
            }
        }catch (Exception e){
            logger.error("获取ios控件位置异常",e);
            throw new ExecuteException("获取ios控件位置失败");
        }
        int x2, y2;
        if ("up".equalsIgnoreCase(direction)) {
            x2 = x1 ;
            y2 = y1 - size;
            y2 = height / 100;
        } else if ("down".equalsIgnoreCase(direction)) {
            x2 = x1;
            y2 = y1 + size;
            y2 = height * 99 / 100;
        } else if ("left".equalsIgnoreCase(direction)) {
            x2 = x1 - size;
            y2 =y1;
        } else if ("right".equalsIgnoreCase(direction)) {
            x2 = x1 + size;
            x2 = width * 99 / 100;
            y2 =y1;
        } else {
            throw new ParamMistakeException("滑动方向仅支持：up、down、left、right");
        }
        logger.debug("开始节点x:{} y:{},结束节点x:{},y:{}",x1,y1,x2,y2);
        new TouchAction(driver).press(PointOption.point(x1, y1))
                .waitAction(WaitOptions.waitOptions(Duration.ofSeconds(200)))
                .moveTo(PointOption.point(x2, y2))
                .release().perform();
    }

    public static void elementSwipe(WebElement element,String direction,  AppiumDriver<WebElement> driver) {
        int width = driver.manage().window().getSize().width;
        int height = driver.manage().window().getSize().height;
        //默认左右滑屏滑动屏幕的4/5
        int size = width * 4 / 5;
        if ("up".equals(direction) || "down".equals(direction)) {
            size = height * 3 / 5;
        }
        elementSwipe(element, direction, driver, size);
    }

    private static void enhancedClearText(WebElement element) {
        element.clear();
        String content = element.getText();
        long start = content.length();
        if (start > 0) {
            logger.warn("当前控件文本无法清空");
        }
    }
}
