package top.octopusyan.manager;

import javafx.application.Platform;
import javafx.scene.control.TextArea;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import top.octopusyan.http.EasyHttp;
import top.octopusyan.http.model.ResponseClass;
import top.octopusyan.manager.http.Api;
import top.octopusyan.model.ApplicatonStore;
import top.octopusyan.model.ProxySetupModel;
import top.octopusyan.utils.EncryptionUtil;
import top.octopusyan.utils.JsoupUtil;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;

/**
 * @author : octopus yan
 * @email : octopus_yan@foxmail.com
 * @description : frp 客户端 工具
 * @create : 2022-4-7 23:19
 */
@Slf4j
public class FrpManager {
    public static final String serverConfigHeader = "yanfrp://";
    public static List<FrpManager> frpcList = new ArrayList<>();
    private static final String YAN_FRP_TEMP_DIR_NAME = "frpclienttmp";
    public static final String FRPC_CLIENT_FILE_NAME = "frpclienttmpfile.exe";
    private static final String FRPC_CONF_PREFIX_NAME = "proxy_";
    private static final String FRPC_CONF_SUFFIX_NAME = ".ini";
    private static final Map<Integer, String> serverConfigraution = new HashMap<>(3);
    /**
     * 应用临时目录 地址
     */
    private static final String YAN_FRP_TEMP_DIR_PATH = System.getProperty("java.io.tmpdir") + System.currentTimeMillis() + YAN_FRP_TEMP_DIR_NAME;
    /**
     * frpc 地址
     */
    private static final String FRPC_CLIENT_FILE_PATH = ApplicatonStore.appDataDirPath + File.separator + FRPC_CLIENT_FILE_NAME;
    private static final File frpconfigDir = new File(YAN_FRP_TEMP_DIR_PATH);
    public static final File frpc = new File(FRPC_CLIENT_FILE_PATH);
    public File frpcConfigFile;

    private final ProxySetupModel model;
    private final TextArea console;
    private ProxyThread thread;
    private Process exec;


    private FrpManager(TextArea console, ProxySetupModel model) {
        this.console = console;
        this.model = model;
    }

    public static FrpManager init(ProxySetupModel model) {
        TextArea console = new TextArea();
        console.setPrefHeight(180);
        console.setMinWidth(200);
        FrpManager frpManager = new FrpManager(console, model);
        frpcList.add(frpManager);
        return frpManager;
    }

    public TextArea getConsole() {
        return console;
    }

    /**
     * 开始
     */
    public void start() {
        thread = new ProxyThread();
        thread.setName(FRPC_CONF_PREFIX_NAME + model.getSort());
        thread.start();
    }

    /**
     * 停止隧道
     */
    public void stop() {
        ThreadPoolManager.getInstance().execute(() -> {
            if (thread != null) {
                do {
                    if (exec != null && exec.isAlive())
                        exec.destroy();
                    thread.setExit(true);
                    thread.interrupt();

                } while (!thread.isInterrupted());

                Platform.runLater(() -> {
                    model.setRunning(false);
                    console.appendText("yan-frp-info:已停止\n");
                    log.info("yan-frp-info-" + model.getProxyName() + ":已停止");
                });

                // 尝试删除缓存配置文件
                if (frpcConfigFile.exists()) frpcConfigFile.delete();
            }
        });
    }

    /**
     * 清理日志
     */
    public void clearLog() {
        if (console != null)
            Platform.runLater(console::clear);
    }

    /**
     * 隧道配置文件地址
     */
    private String getConfigFilePath() {
        return YAN_FRP_TEMP_DIR_PATH + File.separator + FRPC_CONF_PREFIX_NAME + model.getSort() + FRPC_CONF_SUFFIX_NAME;
    }

    /**
     * 初始化隧道配置文件
     */
    private void initProxyConfigFile() throws IOException {
        // 创建配置文件
        frpcConfigFile = new File(getConfigFilePath());
        if (!frpcConfigFile.exists()) {
            FileUtils.createParentDirectories(frpcConfigFile);
            frpcConfigFile.createNewFile();
        }
        frpcConfigFile.deleteOnExit();


        // 写入服务配置
        FileUtils.write(frpcConfigFile, getUserFrpServerConfig(model.get().getNode()), StandardCharsets.UTF_8);
        // 写入隧道配置
        FileUtils.write(frpcConfigFile, getProxyFrpConfig(model), StandardCharsets.UTF_8, true);
    }


    public String getUserFrpServerConfig(int node) {
        boolean isp2p = "xtcp".equals(model.getProxyType()) || "stcp".equals(model.getProxyType());
        String config = serverConfigraution.get(node);
        if (StringUtils.isNotEmpty(config) && !isp2p) return config;

        try {
            if (StringUtils.isEmpty(config)) {
                String result = EasyHttp.builder()
                        .api(Api.getServerConfiguration(node))
                        .execute(new ResponseClass<String>() {
                        });
                config = JsoupUtil.getServerConfiguration(result);
                if (StringUtils.isNotEmpty(config)) serverConfigraution.put(node, config);
            }

            // user配置 会加在访问服务端名称的前边 , 清空让访问到
            if (isp2p && !model.isProvider())
                config = config.replaceAll("user = .*", "");

            return config;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public String getProxyFrpConfig(ProxySetupModel setup) {
        String n = "\n";
        boolean ishttp = setup.getProxyType().contains("http");
        boolean isp2p = "xtcp".equals(setup.getProxyType()) || "stcp".equals(setup.getProxyType());
        // 基础配置
        StringBuilder stringBuilder = new StringBuilder("[");
        // 服务名称
        StringBuilder serverName = new StringBuilder(ApplicatonStore.getAccount() + "_" + setup.getSort());
        // p2p 服务名
        if (isp2p) {
            if (setup.isProvider()) {
                // 提供者
                serverName = new StringBuilder(EncryptionUtil.MD5_16(serverName.toString()));
            } else {
                // 访问者
                serverName = new StringBuilder(setup.getServerName()).append("_visitor");
            }
        } else {
            serverName.append(ApplicatonStore.getAccount()).append("_").append(setup.getSort());
        }
        stringBuilder.append(serverName).append("]\n");
        stringBuilder.append("privilege_mode = true\n")
                .append("type = ").append(setup.getProxyType().contains("http") ? "http" : setup.getProxyType()).append(n);

        if (ishttp) {
            // HTTP / HTTPS
            stringBuilder.append("custom_domains = ").append(setup.getDomain()).append(setup.getDomainSuffix()).append(n)
                    .append("local_ip = ").append(setup.getLocalIp()).append(n)
                    .append("local_port = ").append(setup.getLocalPort()).append(n).append(n);

            if (StringUtils.isNotEmpty(setup.getLocations()))
                stringBuilder.append("locations = ").append(setup.getLocations()).append(n);

            if (StringUtils.isNotEmpty(setup.getHttpUser()))
                stringBuilder.append("http_user = ").append(setup.getHttpUser()).append(n);

            if (StringUtils.isNotEmpty(setup.getHttpPwd()))
                stringBuilder.append("http_pwd = ").append(setup.getHttpPwd()).append(n);

            if (StringUtils.isNotEmpty(setup.getHostHeaderRewrite()))
                stringBuilder.append("host_header_rewrite = ").append(setup.getHostHeaderRewrite()).append(n);

            if (StringUtils.isNotEmpty(setup.getHeader_X_From_Where()))
                stringBuilder.append("header_X-From-Where = ").append(setup.getHeader_X_From_Where()).append(n);

        } else if (isp2p) {
            // xtcp / stcp
            if (!setup.isProvider()) {
                stringBuilder.append("role = visitor").append(n);
                stringBuilder.append("server_name = ").append(setup.getServerName()).append(n);
                stringBuilder.append("bind_addr = ").append(setup.getLocalIp()).append(n);
                stringBuilder.append("bind_port = ").append(setup.getLocalPort()).append(n);
            } else {
                stringBuilder.append("local_ip = ").append(setup.getLocalIp()).append(n);
                stringBuilder.append("local_port = ").append(setup.getLocalPort()).append(n);
            }
            stringBuilder.append("sk = ").append(setup.getSk()).append(n);
        } else {
            // TCP / UDP
            stringBuilder.append("local_ip = ").append(setup.getLocalIp()).append(n)
                    .append("local_port = ").append(setup.getLocalPort()).append(n).append(n)
                    .append("remote_port = ").append(setup.getRemotePort()).append(n);
        }
        // 压缩和加密
        stringBuilder.append("use_encryption = ").append(setup.isUseEncryption()).append(n)
                .append("use_compression = ").append(setup.isUseCompression()).append(n).append(n);

        return stringBuilder.toString();
    }


    /**
     * 初始化frpc客户端文件
     */
    public static void initFrpc() throws IOException {
        // frpc 文件
        if (!frpc.exists()) {
            frpc.createNewFile();

            // 复制 frpc
            copyFileUsingFileStreams(Objects.requireNonNull(FrpManager.class.getResourceAsStream("/static/frpc.exe")));
        }

        // frpc 临时配置文件
        // 存在就删除
        if (frpconfigDir.exists()) FrpManager.clearTmp();
        // 重新创建
        frpconfigDir.mkdir();
        // 发送删除请求
        frpconfigDir.deleteOnExit();
    }

    /**
     * 清理缓存
     */
    public static void clearTmp() {
        try {
            FileUtils.deleteDirectory(frpconfigDir);

            for (File file : Objects.requireNonNull(FileUtils.getTempDirectory().listFiles())) {
                if (file.getPath().contains(YAN_FRP_TEMP_DIR_NAME) && file.canWrite() && file.isDirectory()) {
                    FileUtils.deleteDirectory(file);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            try {
                Runtime.getRuntime().exec("taskkill /im " + FRPC_CLIENT_FILE_NAME + " /f");
                Thread.sleep(1000);
                FileUtils.deleteDirectory(frpconfigDir);
            } catch (InterruptedException | IOException ex) {
                ex.printStackTrace();
            }
        }
    }

    private static void copyFileUsingFileStreams(InputStream input)
            throws IOException {
        OutputStream output = null;
        try {
            output = Files.newOutputStream(FrpManager.frpc.toPath());
            byte[] buf = new byte[1024];
            int bytesRead;
            while ((bytesRead = input.read(buf)) > 0) {
                output.write(buf, 0, bytesRead);
            }
        } finally {
            input.close();
            if (output != null) output.close();
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    class ProxyThread extends Thread {
        private volatile boolean exit = false;

        @Override
        public void run() {
            log.info("yan-frp-info:正在启动");
            Platform.runLater(() -> console.appendText("yan-frp-info:正在启动\n"));
            try {
                // 检查客户端文件
                if (!frpc.exists()) initFrpc();

                // 初始化配置文件
                initProxyConfigFile();

                // 执行命令
                String command = FRPC_CLIENT_FILE_PATH + " -c " + getConfigFilePath();
                log.info(command);
                exec = Runtime.getRuntime().exec(command, null, frpconfigDir);

                // 设置运行状态
                Platform.runLater(() -> model.setRunning(true));

                // 获取标准输出
                BufferedReader readStdout = new BufferedReader(new InputStreamReader(exec.getInputStream()));

                // 逐行读取
                String line;
                while ((line = readStdout.readLine()) != null && !exit) {
                    String finalLine = line;
                    log.info(line);
                    Platform.runLater(() -> console.appendText(finalLine + "\n"));
                }

            } catch (IOException e) {
                e.printStackTrace();
                // TODO 报错
                Platform.runLater(() -> console.appendText("yan-frp-error:" + e.getMessage() + "\n"));
                Platform.runLater(() -> console.appendText("yan-frp-error:启动失败\n"));
                log.info("yan-frp-error:" + e.getMessage());
                log.info("yan-frp-error:启动失败");
            }
        }
    }
}
