package net.northking.cctp.element.mobile.thread;

import com.alibaba.fastjson.JSONObject;
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.ios.IOSDriver;
import net.northking.cctp.element.core.DeviceDriver;
import net.northking.cctp.element.core.IExecuteContext;
import net.northking.cctp.element.core.exception.ExecuteException;
import net.northking.cctp.element.mobile.entity.PointMessage;
import net.northking.cctp.element.mobile.enums.PasswordKeyCode;
import net.northking.cctp.element.mobile.enums.PasswordKeyMove;
import net.northking.cctp.element.mobile.utils.CommonUtils;
import net.northking.cctp.element.mobile.utils.phoneUtils.HandleUtils;
import net.northking.cctp.element.mobile.utils.phoneUtils.IosUtil;
import org.openqa.selenium.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

public class InputPasswordThread implements Callable<Boolean> {

    private Logger logger = LoggerFactory.getLogger(InputPasswordThread.class);

    private String password;
    private IExecuteContext context;
    private AppiumDriver<WebElement> driver;
    private Float preExecuteWait;
    private Float sufExecuteWait;

    private boolean isIos;

    private String preFlag = "0";    //0-字母，1-数字，2-符号

    private String version;

    private final String VERSION_PARAM = "__appVersion";
    private final int VERSION_STR_BEGIN = 0;
    private final int VERSION_STR_END = 1;
    private final int SLEEP_TIME = 100;
    private final int ANDROID_FIND_TIME = 3;
    private final int IOS_FIND_TIME = 30;

    //version5
    private final String ANDROID_BUTTON_XPATH_TEMPLATE = "//*/android.widget.Button[@class='android.widget.Button' and @text='%s']";
    private final String ANDROID_SHIFT_XPATH_TEMPLATE = "//android.widget.LinearLayout[1]/android.widget.LinearLayout[2]/android.widget.LinearLayout[3]/android.widget.Button[1]";
    private final String ANDROID_DONE_XPATH_TEMPLATE = "//android.widget.LinearLayout[1]/android.widget.LinearLayout[2]/android.widget.LinearLayout[4]/android.widget.Button[2]";
    private final String IOS_BUTTON_XPATH_TEMPLATE = "//XCUIElementTypeButton[@name='%s']";
    private final String IOS_SHIFT_XPATH_TEMPLATE = "//XCUIElementTypeButton[@name='caps']";
    private final String IOS_DONE_XPATH_TEMPLATE = "(//XCUIElementTypeButton[@name='done'])[2]";

    //version6
    private final String IOS_BUTTON_XPATH_TEMPLATE_6 = "(//XCUIElementTypeButton[@name=\" \"])[%s]";
    private final String IOS_BUTTON_XPATH_TEMPLATE_DONE_6 = "//XCUIElementTypeButton[@name='%s']";
    private final String IOS_BUTTON_XPATH_TEMPLATE_SELECT_6 = "//XCUIElementTypeStaticText[@name='%s']";
    private final String IOS_BUTTON_XPATH_TEMPLATE_CHANGE_UPPER_6 = "//XCUIElementTypeButton[@name=\"点击两下可切换字母大写\"]";
    private final String IOS_BUTTON_XPATH_TEMPLATE_CHANGE_2_UPPER_6 = "//XCUIElementTypeButton[@name=\"点击两下可切换大写\"]";
    private final String IOS_BUTTON_XPATH_TEMPLATE_CHANGE_LOWER_6 = "//XCUIElementTypeButton[@name=\"点击两下可切换小写\"]";
    private final String ANDROID_BUTTON_XPATH_TEMPLATE_6 = "//android.widget.Button[@text='%s']";
    private final String ANDROID_SHIFT_XPATH_TEMPLATE_6 = "/hierarchy/android.widget.LinearLayout/android.widget.LinearLayout[2]/android.widget.LinearLayout[3]/android.widget.Button[1]";
    private final String ANDROID_KEYBOARD_XPATH_TEMPLATE_6 = "/hierarchy/android.widget.LinearLayout/android.widget.LinearLayout[1]/android.widget.Button[4]";

    public InputPasswordThread(IExecuteContext context, DeviceDriver deviceDriver,  Float preExecuteWait, Float sufExecuteWait, String password) {
        this.password = password;
        this.context = context;
        this.driver = (AppiumDriver<WebElement>) deviceDriver;
        if (this.driver instanceof IOSDriver) {
            isIos = true;
        }
        String appVersion = (String) context.getContextVariable(VERSION_PARAM);
        logger.info("获取到当前app版本为：{}，大版本为：{}",appVersion,this.version);
        this.version = appVersion.substring(VERSION_STR_BEGIN,VERSION_STR_END);
        this.preExecuteWait = preExecuteWait;
        this.sufExecuteWait = sufExecuteWait;
    }

    @Override
    public Boolean call() throws Exception {
        CommonUtils.handlePreExecuteWait(preExecuteWait);
        if (!StringUtils.hasText(password)) {
            throw new ExecuteException("输入的密码不能为空");
        }
        char[] passwordChars = password.toCharArray();
        switch (version) {
            case "5":
                inputVersion5Password(passwordChars);
                break;
            case "6":
                inputVersion6Password(passwordChars);
                break;
            default:
                logger.warn("未知版本：{}",version);
                break;
        }
        CommonUtils.handleSufExecuteWait(sufExecuteWait);
        return true;
    }

    private void inputVersion6Password(char[] passwordChars) {
        PointMessage basePoint = null;
        Integer scale = 2;
        if (isIos) {
            Double windowsSize = IosUtil.getWindowsSize(context, driver);
            basePoint = getPwdMsg(windowsSize.intValue());
            if (null == basePoint) {
                throw new ExecuteException("获取首元素信息为空！");
            }
        }
        for (char passwordChar : passwordChars) {
            logger.debug("输入字符：{}", passwordChar);
            if (isIos) {
                inputIosPasswordChar6New(passwordChar,basePoint,scale);
            } else {
                inputAndroidPasswordChar6(passwordChar);
            }
            try {
                Thread.sleep(SLEEP_TIME);
            } catch (InterruptedException e) {
                logger.error("手机银行版本6输入密码异常：",e);
            }
        }
        if (isIos) {
            PointMessage point = getCurrentPosition("登录", basePoint);
            HandleUtils.clickPassword(driver,point,scale); //点密码
        } else {
            clickByXpath(ANDROID_KEYBOARD_XPATH_TEMPLATE_6);//键盘退出
        }
    }

    /**
     * 根据键盘第一行第一列三个字符确定起始坐标和xy偏移量
     * @return
     */
    private PointMessage getPwdMsg(Integer scale) {
        //获取第一个元素
        String xpath1 = String.format(IOS_BUTTON_XPATH_TEMPLATE_6, 1);
        WebElement element1 = existElement(xpath1, IOS_FIND_TIME);
        PointMessage point = null;
        if (null != element1 && null != element1.getLocation() && null != element1.getRect()) {
            Point location = element1.getLocation();
            Rectangle rect = element1.getRect();
            int firstX = location.getX() + rect.width / 2;
            int firstY = location.getY() + rect.height / 2;
            logger.debug("第一个元素的中心点：({},{})",firstX,firstY);
            point = new PointMessage(firstX,firstY,false);
            //获取第一行第二个元素确定x轴偏移量
            String xpath2 = String.format(IOS_BUTTON_XPATH_TEMPLATE_6, 2);
            WebElement element2 = existElement(xpath2, IOS_FIND_TIME);
            if (null != element2 && null != element2.getLocation()) {
                Point location2 = element2.getLocation();
                Rectangle rect2 = element2.getRect();
                int secondX = location2.getX() + rect2.width / 2;
                int secondY = location2.getY() + rect2.height / 2;
                logger.debug("第二个元素的中心点：({},{})",secondX,secondY);
                point.setxMove(secondX-firstX);
            }
            //获取第一列第二个元素确定y轴偏移量
            String xpath3 = String.format(IOS_BUTTON_XPATH_TEMPLATE_6, 11);
            WebElement element3 = existElement(xpath3, IOS_FIND_TIME);
            if (null != element3 && null != element3.getLocation()) {
                Point location3 = element3.getLocation();
                Rectangle rect3 = element3.getRect();
                int threeX = location3.getX() + rect3.width / 2;
                int threeY = location3.getY() + rect3.height / 2;
                logger.debug("第三个元素的中心点：({},{})",threeX,threeY);
                //y轴偏移量=第一列y2-第一列y1
                point.setyMove(threeY-firstY);
            }
            //根据首元素宽高确定中心位置
            logger.debug("首元素原始信息：x=【{}】,y=【{}】,wid=【{}】,hei=【{}】", firstX,firstY,rect.width,rect.height);
            logger.debug("处理后的首元素位置信息：{}", JSONObject.toJSONString(point));
        }else {
            logger.warn("查找首元素信息为空...");
        }
        return point;
    }

    private PointMessage getCurrentPosition(String passwordChar, PointMessage basePoint) {
        String code = PasswordKeyMove.getCode(passwordChar.toLowerCase());
        PointMessage res = null;
        if (StringUtils.hasText(code)) {
            res = new PointMessage(basePoint.getX(),basePoint.getY(),false);
            int xMode = basePoint.getxMove();
            int yMode = basePoint.getyMove();
            char[] codeArray = code.toCharArray();
            int length = codeArray.length;
            Integer xNum = 0;
            Integer yNum = 0;
            switch (length) {
                //第一行
                case 1:
                    xNum = Integer.valueOf(String.valueOf(codeArray[0]));
                    res.setxMove(xNum * xMode);
                    break;
                //第234行（除开字符第三行）
                case 2:
                    yNum = Integer.valueOf(String.valueOf(codeArray[0]));
                    xNum = Integer.valueOf(String.valueOf(codeArray[1]));
                    res.setyMove(yNum * yMode);
                    res.setxMove(xNum * xMode);
                    break;
                //数字/字符/字母等带负号的位置
                case 3:
                    yNum = Integer.valueOf(String.valueOf(codeArray[1]));
                    xNum = Integer.valueOf(String.valueOf(codeArray[2]));
                    res.setyMove(-(yNum * yMode));
                    res.setxMove(xNum * xMode);
                    break;
                //字符第三行
                case 4:
                    yNum = Integer.valueOf(String.valueOf(codeArray[0]));
                    xNum = Integer.valueOf(String.valueOf(codeArray[1]));
                    res.setyMove(yNum * yMode);
                    res.setxMove(xNum * xMode + xMode / 2 );
                    break;
                default:
                    logger.warn("【{}】偏移码超出范围：{}",passwordChar,code);
            }
            res.setX(res.getX() + res.getxMove());
            res.setY(res.getY()+res.getyMove());
            res.setyMove(0);
            res.setxMove(0);
            logger.debug("字符【{}】位置信息：{}", passwordChar,JSONObject.toJSONString(res));
        }else {
            logger.warn("【{}】偏移码获取为空！",passwordChar);
        }
        return res;
    }

    private void inputIosPasswordChar6New(char passwordChar, PointMessage basePoint, Integer scale) {
        boolean letterOrDigit = Character.isLetterOrDigit(passwordChar);
        if (letterOrDigit) {   //数字或字母
            if ("2".equals(preFlag)) {  //当前在字符页面
                PointMessage point = getCurrentPosition("字母", basePoint);
                HandleUtils.clickPassword(driver,point,scale);
                preFlag = "0";//当前页面是数字界面
            }
            boolean upperCase = Character.isUpperCase(passwordChar); //大写
            if (upperCase) {
                PointMessage point = getCurrentPosition("大写", basePoint);
                HandleUtils.clickPassword(driver,point,scale);
            }
            PointMessage point = getCurrentPosition(String.valueOf(passwordChar), basePoint);
            HandleUtils.clickPassword(driver,point,scale); //点密码
        }else { //字符
            if (!"2".equalsIgnoreCase(preFlag)) {  //没在字符页面
                PointMessage point = getCurrentPosition("字符", basePoint);
                HandleUtils.clickPassword(driver,point,scale);  //点字符
                preFlag = "2";//当前页面是字符界面
            }
            PointMessage point = getCurrentPosition(String.valueOf(passwordChar), basePoint);
            HandleUtils.clickPassword(driver,point,scale); //点密码
        }
    }

    private void inputIosPasswordChar6(char passwordChar) {
        boolean letterOrDigit = Character.isLetterOrDigit(passwordChar);
        String code = PasswordKeyCode.getCode(String.valueOf(passwordChar).toLowerCase());
        String xpath = String.format(IOS_BUTTON_XPATH_TEMPLATE_6, code);
        if (letterOrDigit) {   //数字或字母
            if ("2".equals(preFlag)) {  //当前在字符页面
                String letterXpath = String.format(IOS_BUTTON_XPATH_TEMPLATE_SELECT_6, "字母");
                clickByXpath(letterXpath);  //点字母
                preFlag = "0";//当前页面是数字界面
            }
            if (Character.isLetter(passwordChar)) {
                clickChangeStateIOS(passwordChar);
            }
            clickByXpath(xpath); //点密码
        }else { //字符
            if (!"2".equalsIgnoreCase(preFlag)) {  //没在字符页面
                String charXpath = String.format(IOS_BUTTON_XPATH_TEMPLATE_SELECT_6, "字符");
                clickByXpath(charXpath);  //点字符
                preFlag = "2";//当前页面是数字界面
            }
            clickByXpath(xpath); //点密码
        }
    }

    /**
     * ios点击大小写转换键 找到则返回true
     * @param xpath
     */
    private boolean clickChangeByXpath(String xpath){
        boolean res = false;
        WebElement element = null;
        try {
            Thread.sleep(SLEEP_TIME);
            driver.manage().timeouts().implicitlyWait(6,TimeUnit.SECONDS);
            element = driver.findElement(By.xpath(xpath));
        } catch (Exception e) {
            logger.error("查找大小写元素异常：",e);
        }
        //元素存在即点击
        if (null != element) {
            element.click();
            res = true;
            logger.debug("元素【{}】已点击...");
        }
        return res;
    }

    /**
     * 根据字符判断是否点击转换大小写 IOS
     * @param passwordChar
     */
    private void clickChangeStateIOS(char passwordChar) {
        try {
            boolean lowerCase = Character.isLowerCase(passwordChar); //小写字母
            if (!lowerCase) {
                //大写字母，有转大写按钮即点击
                boolean isClick = clickChangeByXpath(IOS_BUTTON_XPATH_TEMPLATE_CHANGE_UPPER_6);//切换大写
                if (!isClick) {
                    //大写字母，有第二种转大写按钮即点击
                    clickChangeByXpath(IOS_BUTTON_XPATH_TEMPLATE_CHANGE_2_UPPER_6); //切换大写
                }
            } else {
                //小写字母，有转小写按钮即点击
                clickChangeByXpath(IOS_BUTTON_XPATH_TEMPLATE_CHANGE_LOWER_6); //切换小写
            }
        }catch (ExecuteException e) {
            logger.warn("转大小写按钮查找失败！");
        }catch (Exception e) {
            logger.error("点击转大小写按钮异常：",e);
        }
    }

    private void inputAndroidPasswordChar6(char passwordChar) {
        String xpath = String.format(ANDROID_BUTTON_XPATH_TEMPLATE_6, passwordChar);
        boolean isNumber = Character.isDigit(passwordChar);
        if (isNumber) {   //数字
            if (!"1".equals(preFlag)) {  //当前在数字页面
                String numberXpath = String.format(ANDROID_BUTTON_XPATH_TEMPLATE_6, "数字");
                clickByXpath(numberXpath);  //点数字
                preFlag = "1";//当前页面是数字界面
            }
            clickByXpath(xpath);  //点密码
            return;
        }
        boolean letter = Character.isLetter(passwordChar);
        if (letter) {  //字母
            if (!"0".equals(preFlag)) {
                String numberXpath = String.format(ANDROID_BUTTON_XPATH_TEMPLATE_6, "字母");
                clickByXpath(numberXpath);  //点字母页面
                preFlag = "0";//当前页面是数字界面
            }
            //大写字母则需先转大写
            boolean upperCase = Character.isUpperCase(passwordChar);
            if (upperCase) {
                clickByXpath(ANDROID_SHIFT_XPATH_TEMPLATE_6);
            }
            clickByXpath(xpath);  //点字母
        } else {  //特殊字符
            if (!"2".equals(preFlag)) {    //不是特殊字符，切换到特殊字符
                String numberXpath = String.format(ANDROID_BUTTON_XPATH_TEMPLATE_6, "字符");
                clickByXpath(numberXpath);  //点字符页面
                preFlag = "2";//当前页面是数字界面
            }
            clickByXpath(xpath);  //点字母
        }
    }

    private void inputVersion5Password(char[] passwordChars) {
        for (char passwordChar : passwordChars) {
            if (isIos) {
                inputIosPasswordChar5(passwordChar);
            } else {
                inputAndroidPasswordChar5(passwordChar);
            }
            try {
                Thread.sleep(SLEEP_TIME);
            } catch (InterruptedException e) {
                logger.error("手机银行版本5输入密码异常：",e);
            }
        }
        if (isIos) {
            String letterXpath = String.format(IOS_BUTTON_XPATH_TEMPLATE, "字母");
            clickByXpath(letterXpath);  //点字母页面
            clickByXpath(IOS_DONE_XPATH_TEMPLATE);//完成
        } else {
            String letterXpath = String.format(ANDROID_BUTTON_XPATH_TEMPLATE, "字母");
            clickByXpath(letterXpath);  //点字母页面
            clickByXpath(ANDROID_DONE_XPATH_TEMPLATE);//完成
        }
    }

    private void inputIosPasswordChar5(char passwordChar) {
        String xpath = String.format(IOS_BUTTON_XPATH_TEMPLATE, passwordChar);
        boolean isNumber = Character.isDigit(passwordChar);
        if (isNumber) {   //数字
            if (!"1".equals(preFlag)) {  //当前在数字页面
                String numberXpath = String.format(IOS_BUTTON_XPATH_TEMPLATE, "数字");
                clickByXpath(numberXpath);  //点数字
                preFlag = "1";//当前页面是数字界面
            }
            clickByXpath(xpath);  //点密码
            return;
        }
        boolean letter = Character.isLetter(passwordChar);
        if (letter) {  //字母
            if (!"0".equals(preFlag)) {
                String numberXpath = String.format(IOS_BUTTON_XPATH_TEMPLATE, "字母");
                clickByXpath(numberXpath);  //点字母页面
                preFlag = "0";//当前页面是数字界面
            }
            boolean lowerCase = Character.isLowerCase(passwordChar);
            if (!lowerCase) {
                clickByXpath(IOS_SHIFT_XPATH_TEMPLATE);  //点大写
            }
            clickByXpath(xpath);  //点字母
        } else {  //特殊字符
            if (!"2".equals(preFlag)) {    //不是特殊字符，切换到特殊字符
                String numberXpath = String.format(IOS_BUTTON_XPATH_TEMPLATE, "字符");
                clickByXpath(numberXpath);  //点字母页面
                preFlag = "2";//当前页面是数字界面
            }
            clickByXpath(xpath);  //点字母
        }
    }

    private void inputAndroidPasswordChar5(char passwordChar) {
        String xpath = String.format(ANDROID_BUTTON_XPATH_TEMPLATE, passwordChar);
        boolean isNumber = Character.isDigit(passwordChar);
        if (isNumber) {   //数字
            if (!"1".equals(preFlag)) {  //当前在数字页面
                String numberXpath = String.format(ANDROID_BUTTON_XPATH_TEMPLATE, "数字");
                clickByXpath(numberXpath);  //点数字
                preFlag = "1";//当前页面是数字界面
            }
            clickByXpath(xpath);  //点密码
            return;
        }
        boolean letter = Character.isLetter(passwordChar);
        if (letter) {  //字母
            if (!"0".equals(preFlag)) {
                String numberXpath = String.format(ANDROID_BUTTON_XPATH_TEMPLATE, "字母");
                clickByXpath(numberXpath);  //点字母页面
                preFlag = "0";//当前页面是数字界面
            }
            boolean lowerCase = Character.isLowerCase(passwordChar);
            if (!lowerCase) {
                clickByXpath(ANDROID_SHIFT_XPATH_TEMPLATE);  //点大写
            }
            clickByXpath(xpath);  //点字母
        } else {  //特殊字符
            if (!"2".equals(preFlag)) {    //不是特殊字符，切换到特殊字符
                String numberXpath = String.format(ANDROID_BUTTON_XPATH_TEMPLATE, "字符");
                clickByXpath(numberXpath);  //点字母页面
                preFlag = "2";//当前页面是数字界面
            }
            clickByXpath(xpath);  //点字母
        }
    }

    private void clickByXpath(String xpath){
        try {
            Thread.sleep(SLEEP_TIME);
        } catch (InterruptedException e) {
            logger.error("输入密码前等待异常：",e);
        }
        WebElement element = driver.findElement(By.xpath(xpath));
        if (null == element) {
            throw new ExecuteException("未查找到元素");
        }
        element.click();
    }


    /**
     * 根据xpath判断元素是否存在
     * @return true为是
     */
    private WebElement existElement(String xpath, int findTime){
        WebElement element = null;
        long time1 = System.currentTimeMillis();
        try {
            Thread.sleep(SLEEP_TIME);
            driver.manage().timeouts().implicitlyWait(findTime, TimeUnit.SECONDS);
            element = driver.findElement(By.xpath(xpath));
        } catch (Exception e) {
            logger.error("查找元素异常：",e);
        }
        long time2 = System.currentTimeMillis();
        logger.debug("查找元素【{}】时间：{}",xpath,(time2-time1)/1000);
        return element;
    }
}
