package com.priusis.controller;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ConcurrentHashSet;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.StreamProgress;
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.cache.InstalledProgramCache;
import com.priusis.lib.R;
import com.priusis.util.MacAddrUtil;
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 javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
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.install-status-upissue}")
    private String installStatusUpIssueUrl;

    @Value(value = "${apq.url.upgrade-status-upissue}")
    private String upgradeStatusUpIssueUrl;

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





    @Resource
    private InstalledProgramCache installedProgramCache;

    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 {
        String mac = MacAddrUtil.get();
        if (StrUtil.isBlank(mac)) return R.error("未获取到MAC地址");
        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<Map<String, String>> page = new Page<>();
        page.setCurrent(current).setSize(size).setTotal(rsp.getLong("total"));
        JSONArray rows = rsp.getJSONArray("rows");
        if (CollUtil.isEmpty(rows)) return R.success(page.setRecords(Collections.emptyList()));
        List<Map<String, String>> programs = installedProgramCache.getPrograms();
        List<Map<String, String>> list = rows.stream().map(JSONObject.class::cast)
                .map(o -> {
                    Map<String, String> map = new HashMap<>();
                    map.put("id", o.getStr(type.equals("install") ? "installId" : "upgradeId"));
                    map.put("name", StrUtil.subBefore(o.getStr("fileName"), '.', true));
                    map.put("size", o.getStr("fileSize"));
                    map.put("version", o.getStr("version"));
                    map.put("localVersion", null);
                    map.put("url", o.getStr("url"));
                    Long id = Convert.toLong(map.get("id"));
                    String key = type + "-" + id;
                    map.put("status", SET.contains(key) ? "1" : MAP.containsKey(key) ? "2" : "0");
                    return map;
                })
                .peek(m -> programs.stream()
                        .collect(Collectors.toMap(p -> StrUtil.similar(p.get("name").toLowerCase(), m.get("name").toLowerCase()),
                                p -> p.getOrDefault("version", ""), (l, r) -> r))
                        .entrySet()
                        .stream()
                        .max(Map.Entry.comparingByKey())
                        .filter(e -> e.getKey() >= MIN_MATCH)
                        .filter(e -> StrUtil.isNotBlank(e.getValue()))
                        .ifPresent(e -> m.put("localVersion", e.getValue())))
                .collect(Collectors.toList());
        return R.success(page.setRecords(list));
    }

    private static final Set<String> SET = new ConcurrentHashSet<>();
    private static final Map<String, Thread> MAP = new ConcurrentHashMap<>();


    @GetMapping(value = "{type}/download_and_install")
    public void downloadAndInstall(@PathVariable @Pattern(regexp = "install|upgrade") String type,
                                   @RequestParam @NotNull Long id,
                                   @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.#####");
        String key = type + "-" + id;
        File file = rsp.writeBodyForFile(dir, new StreamProgress() {
            @SneakyThrows
            @Override
            public void start() {
                SET.add(key);
                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();
                SET.remove(key);
            }
        });
        MAP.put(key, new Thread(() -> {
            int exitVal = -1;
            try {
                exitVal = Runtime.getRuntime().exec("cmd /c \"" + file.getPath() +"\"").waitFor();
            } catch (Exception e) {
                log.error("执行安装文件 {} 失败", file.getPath(), e);
            }
            MAP.remove(key);
            if (exitVal == 0) {
                String reqUrl = gateway + StrUtil.format(type.equals("install") ? installStatusUpIssueUrl : upgradeStatusUpIssueUrl,
                        MacAddrUtil.get(), id);
                HttpUtil.post(reqUrl, (String) null);
            }
        }));
        MAP.get(key).start();
    }

    @GetMapping(value = "{type}/is_installing")
    public R<Boolean> isInstalling(@PathVariable @Pattern(regexp = "install|upgrade") String type,
                                   @RequestParam @NotNull Long id) {
        return R.success(MAP.containsKey(type + "-" + id));
    }

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


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