package net.northking.cctp.upperComputer.driver.harmony.hdc;


import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;

public class Hdc {

    private static final Logger log = LoggerFactory.getLogger(Hdc.class);
    /**
     * HDC监听地址
     */
    private static String HOST = "127.0.0.1";

    /**
     * HDC监听端口
     */
    private static int PORT = 8710;

    private static Hdc instance = null;

    public static Hdc getInstance() {
        if (instance == null) {
            instance = new Hdc();
        }
        return instance;
    }

    /**
     * HDC可执行文件路径
     */
//    private static String hdcPath = null;

    private HDCDeviceObserver hdcDeviceObserver = null;

    private Hdc() {
        init();
    }

    public static void setPORT(int PORT) {
        Hdc.PORT = PORT;
    }

    public static void setHOST(String HOST) {
        Hdc.HOST = HOST;
    }

    /**
     * 启动hdc-server
     *
     * @throws FileNotFoundException hdcPath指向的文件不存在
     */
    public static void init() {
        if (!startHdcDaemon()) {
            throw new RuntimeException("无法启动HDC守护进程");
        }
    }

    /**
     * 启动HDC守护进程
     *
     * @return 是否成功启动
     */
    private static boolean startHdcDaemon() {
        ProcessBuilder processBuilder = new ProcessBuilder("hdc", "list","targets");
        Process process;
        try {
            process = processBuilder.start();
        } catch (IOException e) {
            log.error("启动hdc失败", e);
            return false;
        }
        boolean runResult = false;
        try {
            runResult = process.waitFor(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            log.warn("等待hdc启动的过程中被中断");
            return false;
        }
        if (!runResult) {
            return false;
        }
        return process.exitValue() == 0;
    }

    /**
     * 创建HDC会话
     *
     * @return HDC会话，null为失败
     */
    public HDCSession createSession() {
        Socket socket;
        try {
            socket = new Socket(HOST, PORT);
        } catch (IOException e) {
            log.error("创建HDC会话失败，连接到hdc时发生IO错误，host:{} port:{}", HOST, PORT, e);
            return null;
        }
        HDCSession session;
        try {
            session = new HDCSession(socket);
        } catch (IOException e) {
            log.error("创建HDC会话失败，连接到hdc后发生IO错误", e);
            return null;
        }
        return session;
    }

    public HDCSession createSession(HDCDevice hdcDevice) {
        Socket socket;
        try {
            socket = new Socket(HOST, PORT);
        } catch (IOException e) {
            log.error("创建设备{}的HDC会话失败，连接到hdc时发生IO错误，host:{} port:{}", hdcDevice.getConnectKey(), HOST, PORT, e);
            return null;
        }
        HDCSession session;
        try {
            session = new HDCSession(socket, hdcDevice.getConnectKey());
        } catch (IOException e) {
            log.error("创建HDC会话失败，连接到hdc后发生IO错误", e);
            return null;
        }
        return session;
    }

    /**
     * 获得hdc版本
     *
     * @return hdc版本，null则为获取失败
     */
    public String version() {
        HDCSession session = createSession();
        String rawVersion = null;
        try {
            rawVersion = session.sendHdcCommand("version");
        } catch (IOException e) {
            session.close();
            return null;
        }
        if (rawVersion == null) {
            return null;
        }
        if (!rawVersion.startsWith("Ver: ")) {
            return null;
        }
        return rawVersion.substring("Ver: ".length());
    }

    public ArrayList<HDCDevice> listTargets() {
        HDCSession session = createSession();
        if (session == null) {
            return new ArrayList<>();
        }
        return listTargets(session);
    }

    /**
     * 通过指定session获得当前设备列表
     *
     * @param session HDC会话
     * @return 设备列表，如果发生错误，则为null
     */
    public ArrayList<HDCDevice> listTargets(HDCSession session) {
        String replyContent;
        try {
            replyContent = session.sendHdcCommand("list targets -v");
        } catch (IOException e) {
            log.error("获取设备列表失败，连接到hdc后发生IO错误", e);
            return null;
        }
        if (replyContent.length() < 10) return new ArrayList<>();
        replyContent = replyContent.replaceAll("\t", " ");
        ArrayList<HDCDevice> result = new ArrayList<HDCDevice>();
        String[] lines = replyContent.split("\n");
        for (String line : lines) {
            if (line.length() > 15) {
                HDCDevice device = parseDeviceLine(line);
                result.add(device);
            }
        }
        return result;
    }

    /**
     * 推送本地文件到设备上
     *
     * @param hdcDevice  要推送到的设备
     * @param file       要推送的文件
     * @param devicePath 设备上的路径
     * @return 是否推送成功
     */
    public boolean sendFile(HDCDevice hdcDevice, File file, String devicePath) {
        if (!file.exists()) {
            log.error("文件不存在");
            return false;
        }
        if (!file.canRead()) {
            log.error("文件不可读");
            return false;
        }
        ProcessBuilder processBuilder = new ProcessBuilder("hdc", "-t", hdcDevice.getConnectKey(), "file", "send", file.getAbsolutePath(), devicePath);
        Process process;
        try {
            process = processBuilder.start();
        } catch (IOException e) {
            log.error("推送文件{}到设备{}失败：发生IO错误", file.getAbsolutePath(), hdcDevice.getConnectKey(), e);
            return false;
        }
        //退出码无法判断是否成功，始终为0
        BufferedReader reader = null;
        try {
            InputStream inputStream = process.getInputStream();
            reader = new BufferedReader(new InputStreamReader(inputStream));

            String line;
            while ((line = reader.readLine()) != null) {
                if (line.startsWith("[Fail]")) {
                    log.error("推送文件{}到设备{}失败:{}", file.getAbsolutePath(), hdcDevice.getConnectKey(), line);
                    return false;
                } else if (line.startsWith("FileTransfer finish")) {
                    return true;
                }
            }
        } catch (IOException e) {
            log.warn("中断推送文件{}到设备{}，设备上可能存在文件残留", file.getAbsolutePath(), hdcDevice.getConnectKey());
            return false;
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (Exception e) {
                    log.error("关闭读取流失败", e);
                }
            }
        }
        log.error("推送文件{}到设备{}失败:{}", file.getAbsolutePath(), hdcDevice.getConnectKey(), "未知错误");
        return false;
    }

    /**
     * 从设备上拉取文件到本地
     *
     * @param hdcDevice  要拉取文件的设备
     * @param devicePath 要拉取的文件在设备上的路径
     * @param file       要保存到的文件
     * @return 是否成功
     */
    public boolean receiveFile(HDCDevice hdcDevice, String devicePath, File file) {
        String localPath = file.getAbsolutePath();
        if (file.exists()) {
            if (!file.canWrite()) {
                String errorReason = "目标文件无法覆盖";
                if (file.isDirectory()) {
                    errorReason = "目标目录无法写入";
                }
                log.error("从设备{}接收文件{}失败:{}", hdcDevice.getConnectKey(), devicePath, errorReason);
                return false;
            }
            if (file.isDirectory()) {
                localPath += "/";
            }
        } else {
            File parentFile = file.getParentFile();
            if (parentFile != null) {
                if (!parentFile.exists()) {
                    log.error("从设备{}接收文件{}失败:{}，父目录不存在", hdcDevice.getConnectKey(), devicePath, "父目录不存在");
                    return false;
                }
                if (!parentFile.canWrite()) {
                    log.error("从设备{}接收文件{}失败:{}，父目录无法写入", hdcDevice.getConnectKey(), devicePath, "父目录无法写入");
                    return false;
                }
            }
        }
        ProcessBuilder processBuilder = new ProcessBuilder("hdc", "-t", hdcDevice.getConnectKey(), "file", "recv", devicePath, localPath);
        Process process;
        try {
            process = processBuilder.start();
        } catch (IOException e) {
            log.error("从设备{}接收文件{}失败:发生IO错误", hdcDevice.getConnectKey(), devicePath, e);
            return false;
        }
        //退出码无法判断是否成功，始终为0
        BufferedReader reader = null;
        try {
            InputStream inputStream = process.getInputStream();
            reader = new BufferedReader(new InputStreamReader(inputStream));
            String line;
            StringBuilder builder = new StringBuilder();
            while ((line = reader.readLine()) != null) {
                builder.append(line);
            }
            String result = builder.toString();
            log.debug("从手机拉图片打印：{}",result);
            if (result.startsWith("[Fail]")) {
                log.error("从设备{}接收文件{}失败:{}", hdcDevice.getConnectKey(), devicePath, line);
                return false;
            } else if (result.contains("FileTransfer finish")) {
                return true;
            }
        } catch (IOException e) {
            log.warn("中断从设备{}接收文件{}，可能存在文件残留", hdcDevice.getConnectKey(), file.getAbsolutePath());
            return false;
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    log.error("关闭读取流失败", e);
                }
            }
        }
        log.error("推送从设备{}接收文件{}:{}",hdcDevice.getConnectKey(), file.getAbsolutePath(), "未知错误");
        return false;
    }

    /**
     * 安装单个hap文件
     *
     * @param hdcDevice 要安装hap的设备
     * @param hapFile   hap文件
     * @return 是否安装成功
     */
    public boolean install(HDCDevice hdcDevice, File hapFile) {
        if (!hapFile.exists()) {
            log.error("安装失败，HAP文件不存在");
            return false;
        }
        if (!hapFile.canRead()) {
            log.error("安装失败，HAP文件不可读");
            return false;
        }

        ProcessBuilder processBuilder = new ProcessBuilder("hdc", "-t", hdcDevice.getConnectKey(), "install", hapFile.getAbsolutePath());
        Process process;
        BufferedReader reader = null;
        try {
            process = processBuilder.start();
            reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                if (line.startsWith("[Fail]")||line.contains("failed")) {
                    log.error("设备{}安装HAP文件失败:{}", hdcDevice.getConnectKey(), line);
                    return false;
                }
            }
        } catch (IOException e) {
            log.error("设备{}安装HAP文件出错:", hdcDevice.getConnectKey(), e);
            return false;
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    log.warn("关闭流失败", e);
                }
            }
        }

        return true;
    }

    /**
     * 卸载指定Package应用
     *
     * @param hdcDevice          要卸载的设备
     * @param packageName        要卸载的包名
     * @param keepData           是否保留数据
     * @param removeSharedBundle 是否移除共享库
     * @return true表示成功，false表示失败
     */
    public boolean uninstall(HDCDevice hdcDevice, String packageName, boolean keepData, boolean removeSharedBundle) {
        boolean result = false;
        try (HDCSession session = createSession(hdcDevice)) {
            StringBuilder commandBuilder = new StringBuilder("uninstall ");
            if (keepData) {
                commandBuilder.append("-k ");
            }
            if (removeSharedBundle) {
                commandBuilder.append("-s ");
            }
            commandBuilder.append(packageName);
            String returnText = null;
            try {
                returnText = session.sendHdcCommand(commandBuilder.toString());
            } catch (IOException e) {
                log.error("设备{}卸载HAP文件发生IO错误：", hdcDevice.getConnectKey(), e);
            }
            if (returnText != null) {
                result = !returnText.contains("failed");
                if (!result) {
                    log.error("设备{}卸载HAP文件失败：{}", hdcDevice.getConnectKey(), returnText);
                }
            }
        }
        return result;
    }

    /**
     * 将list target -v的输出行转变为HDCDevice对象
     *
     * @param line hdc输出的原始行
     * @return 解析到的HDCDevice对象
     */
    private HDCDevice parseDeviceLine(String line) {
        String[] wordList = line.split(" ");
        return new HDCDevice(
                wordList[0],
                HDCConnectType.getConnectType(wordList[2]),
                HDCConnectStatus.getHDCConnectStatus(wordList[3]),
                wordList[4]
        );
    }

    /**
     * 开始监控设备变化
     */
    public void startObserveDevice() {
        if (hdcDeviceObserver != null) throw new IllegalStateException("上一个HDC设备变化监控没有关闭");
        hdcDeviceObserver = new HDCDeviceObserver();
        hdcDeviceObserver.start();
    }

    /**
     * 停止监控设备变化
     */
    public void stopObserveDevice() {
        if (hdcDeviceObserver != null) {
            hdcDeviceObserver.stop();
            hdcDeviceObserver = null;
        }
    }

    public boolean isObserveDeviceRunning() {
        return hdcDeviceObserver != null;
    }


    public boolean createForward(ForwardRule forwardRule) {
        boolean result = false;
        HDCDevice hdcDevice = null;
        for (HDCDevice device : listTargets()) {
            if (device.getConnectKey().equals(forwardRule.getConnectKey())) {
                hdcDevice = device;
                break;
            }
        }
        if (hdcDevice == null) {
            log.warn("创建端口转发时设备{}不存在", forwardRule.getConnectKey());
            return false;
        }
        if (hdcDevice.getConnectStatus() != HDCConnectStatus.CONNECTED) {
            log.warn("创建端口转发时设备{}未连接", forwardRule.getConnectKey());
            return false;
        }
        try (HDCSession session = createSession(hdcDevice)) {
            String returnText = null;
            String command = "fport";
            if (forwardRule.isReverse()) {
                command = "rport";
            }
            try {
                returnText = session.sendHdcCommand(command + " " + forwardRule.taskString());
            } catch (IOException e) {
                log.error("创建端口转发失败", e);
            }
            if (returnText != null && returnText.startsWith("Forwardport result:OK")) {
                result = true;
            }else{
                log.debug("端口转发创建失败，返回："+returnText);
            }
        }
        return result;
    }

    /**
     * 获取端口转发记录
     *
     * @return 端口转发记录
     */
    public ArrayList<ForwardRule> listForward() {
        ArrayList<ForwardRule> rules = new ArrayList<ForwardRule>();
        try (HDCSession session = createSession()) {
            String returnText = null;
            try {
                returnText = session.sendHdcCommand("fport ls");
            } catch (IOException e) {
                log.error("获取端口转发记录失败，发送命令 fport ls 失败", e);
            }
            if (returnText != null) {
                String[] lines = returnText.split("\n");
                for (String line : lines) {
                    if (!line.startsWith("[")) {
                        rules.add(new ForwardRule(line));
                    }
                }
            }
        }
        return rules;
    }

    /**
     * 移除已存在的端口转发规则
     *
     * @param forwardRule 端口转发规则
     * @return 是否操作成功
     */
    public boolean removeForwardRule(ForwardRule forwardRule) {
        boolean result = false;
        try (HDCSession session = createSession()) {
            String returnText = null;
            try {
                returnText = session.sendHdcCommand("fport rm " + forwardRule.taskString());
            } catch (IOException e) {
                log.error("移除端口转发记录失败，发送命令 fport rm 失败", e);
            }
            if (returnText != null) {
                String[] lines = returnText.split("\n");
                for (String line : lines) {
                    if (line.startsWith("Remove forward ruler success")) {
                        result = true;
                        break;
                    }
                }
            }
        }
        return result;
    }

    /**
     * 在指定的设备上执行 shell
     *
     * @param hdcDevice 设备对象
     * @param shell     命令行字符串，如果null则进入shell终端交互会话
     * @return HDCSession，null为通过HDC执行shell动作失败
     */
    public HDCSession shell(HDCDevice hdcDevice, String shell) {
        HDCSession session = createSession(hdcDevice);
        if (shell == null || StringUtils.isBlank(shell)) {
            try {
                session.sendHdcData(("shell\0").getBytes(StandardCharsets.UTF_8));
            } catch (IOException e) {
                log.error("创建shell会话失败");
                session.close();
                return null;
            }
        } else {
            try {
                session.sendHdcData(("shell " + shell + "\0").getBytes(StandardCharsets.UTF_8));
            } catch (IOException e) {
                log.error("通过HDC执行shell失败：{}", shell);
                session.close();
                return null;
            }
        }
        return session;
    }

    public void addHDCDeviceListener(HDCDeviceListener listener) {
        HDCDeviceObserver observer = hdcDeviceObserver;
        if (observer == null) throw new IllegalStateException("没有开启监控设备变化");
        observer.addHDCDeviceListener(listener);
    }

    public void removeHDCDeviceListener(HDCDeviceListener listener) {
        HDCDeviceObserver observer = hdcDeviceObserver;
        if (observer == null) throw new IllegalStateException("没有开启监控设备变化");
        observer.removeHDCDeviceListener(listener);
    }

    public boolean isObservingDevice() {
        return hdcDeviceObserver != null;
    }

}
