package com.priusis.controller;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.StreamProgress;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.priusis.lib.R;
import com.priusis.util.SysConfigUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import oshi.hardware.NetworkIF;

import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.text.DecimalFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author yangli
 * @since 2021/09/07
 */
@Slf4j
@Validated
@RestController
@RequestMapping(value = "program")
public class ProgramController {
    @Value(value = "${apq.iot-gateway}")
    private String gateway;

    @Value(value = "${apq.url.install}")
    private String installUrl;

    @Value(value = "${apq.url.upgrade}")
    private String upgradeUrl;

    @Value(value = "${apq.url.add-program}")
    private String addProgramUrl;

    private static List<Map<String, String>> programList;

    private static final double MIN_MATCH = 0.5;

    @GetMapping(value = "{type}/list")
    public R<Page<Map<String, String>>> list(@PathVariable @Pattern(regexp = "install|upgrade") String type,
                                             @RequestParam(required = false, defaultValue = "1") Integer current,
                                             @RequestParam(required = false, defaultValue = "10") Integer size)
            throws IOException {
        Page<Map<String, String>> page = new Page<>();
        page.setCurrent(current).setSize(size);
        String mac = getMacAddress();
        if (StrUtil.isBlank(mac)) return R.success(page.setTotal(0).setRecords(Collections.emptyList()));
        String url = gateway + StrUtil.format(type.equals("install") ? installUrl : upgradeUrl, mac, current, size);
        JSONObject rsp = JSONUtil.parseObj(HttpUtil.get(url));
        if (ObjectUtil.notEqual(rsp.getInt("code"), 0)) return R.error(rsp.getStr("msg"));
        page.setTotal(rsp.getLong("total"));
        JSONArray rows = rsp.getJSONArray("rows");
        if (CollUtil.isEmpty(rows)) return R.success(page.setRecords(Collections.emptyList()));
        if (programList == null) loadProgramListJob();
        List<Map<String, String>> programs = programList;
        List<Map<String, String>> list = rows.stream().map(JSONObject.class::cast)
                .map(o -> MapUtil.<String, String>builder()
                        .put("id", o.getStr("id"))
                        .put("name", StrUtil.subBefore(o.getStr("fileName"), '.', true))
                        .put("size", o.getStr("fileSize"))
                        .put("version", o.getStr("version"))
                        .put("localVersion", null)
                        .put("url", o.getStr("url"))
                        .build())
                .peek(m -> programs.stream()
                        .collect(Collectors.toMap(p -> StrUtil.similar(p.get("name").toLowerCase(), m.get("name").toLowerCase()),
                                p -> p.get("version"), (l, r) -> r))
                        .entrySet()
                        .stream()
                        .max(Map.Entry.comparingByKey())
                        .filter(e -> e.getKey() >= MIN_MATCH)
                        .ifPresent(e -> m.put("localVersion", e.getValue())))
                .collect(Collectors.toList());
        return R.success(page.setRecords(list));
    }


    @GetMapping(value = "download_and_install")
    public void downloadAndInstall(@RequestParam @NotBlank String url, HttpServletResponse response) throws IOException {
        File dir = new File(SysConfigUtil.getProperty("download.path",
                System.getProperty("user.home") + File.separator + "Downloads"));
        if (!dir.exists()) dir.mkdirs();
        HttpResponse rsp = HttpUtil.createGet(url).execute(true);
        double length = rsp.contentLength();
        DecimalFormat decimalFormat = new DecimalFormat("0.#####");
        File file = rsp.writeBodyForFile(dir, new StreamProgress() {
            @SneakyThrows
            @Override
            public void start() {
                response.getWriter().write("0\n");
            }

            @SneakyThrows
            @Override
            public void progress(long progressSize) {
                response.getWriter().write(decimalFormat.format(progressSize / length) + "\n");
            }

            @SneakyThrows
            @Override
            public void finish() {
                response.getWriter().close();
            }
        });
        try {
            Runtime.getRuntime().exec("cmd /c " + file.getPath());
        } catch (Exception e) {
            log.error("执行安装文件 {} 失败", file.getPath(), e);
        }
    }

    @GetMapping(value = "get_version")
    public R<String> getVersion(@RequestParam @NotBlank String name) throws IOException {
        if (programList == null) loadProgramListJob();
        return programList.stream()
                .collect(Collectors.toMap(p -> StrUtil.similar(p.get("name").toLowerCase(), name.toLowerCase()),
                        p -> p.get("version"), (l, r) -> r))
                .entrySet()
                .stream()
                .max(Map.Entry.comparingByKey())
                .filter(e -> e.getKey() >= MIN_MATCH)
                .map(Map.Entry::getValue)
                .map(R::success)
                .orElseGet(R::success);
    }


    @Scheduled(fixedDelay = 3000)
    protected void loadProgramListJob() throws IOException {
        List<Map<String, String>> programs = new ArrayList<>();
        Process process = Runtime.getRuntime()
                .exec("cmd /c reg query HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\");
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "GBK"))) {
            String key;
            while ((key = reader.readLine()) != null) {
                Map<String, String> info = queryProgramInfo(key);
                if (info.containsKey("name")) programs.add(info);
            }
            process.destroy();
        }
        programList = programs;
    }

    private Map<String, String> queryProgramInfo(String key) throws IOException {
        Map<String, String> info = new HashMap<>();
        Process process = Runtime.getRuntime().exec("cmd /c reg query " + key);
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "GBK"))) {
            reader.readLine();
            reader.readLine();
            String line;
            while ((line = reader.readLine()) != null) {
                line = line.trim();
                if (line.startsWith("DisplayName")) {
                    info.put("name", line.replace("DisplayName", "").replace("REG_SZ", "").trim());
                } else if (line.startsWith("DisplayVersion")) {
                    info.put("version", line.replace("DisplayVersion", "").replace("REG_SZ", "").trim());
                }
            }
            process.destroy();
        }
        return info;
    }


    @Scheduled(fixedDelay = 60000)
    protected void addProgram2Platform() throws IOException {
        if (programList == null) loadProgramListJob();
        String mac = getMacAddress();
        if (StrUtil.isBlank(mac)) {
            log.warn("未获取到MAC地址");
            return;
        }
        List<String> programNames = programList.stream().map(p -> p.get("name")).collect(Collectors.toList());
        HttpUtil.post(gateway + addProgramUrl,
                JSONUtil.createObj().set("macAddress", mac).set("programList", programNames).toString());
    }

    private String getMacAddress() throws UnknownHostException, SocketException {
        NetworkIF networkIF = new NetworkIF();
        networkIF.setNetworkInterface(NetworkInterface.getByInetAddress(InetAddress.getLocalHost()));
        return networkIF.getMacaddr();
    }
}
