
package cn.ysk.cashier.service.impl;

import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.ysk.cashier.domain.Log;
import cn.ysk.cashier.domain.LogVO;
import cn.ysk.cashier.repository.LogRepository;
import cn.ysk.cashier.service.LogService;
import cn.ysk.cashier.service.SpelUtil;
import cn.ysk.cashier.service.dto.LogQueryCriteria;
import cn.ysk.cashier.service.dto.LogQueryCriteriaExt;
import cn.ysk.cashier.service.mapstruct.LogErrorMapper;
import cn.ysk.cashier.service.mapstruct.LogSmallMapper;
import cn.ysk.cashier.utils.*;
import lombok.RequiredArgsConstructor;
import cn.ysk.cashier.utils.*;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author Zheng Jie
 * @date 2018-11-24
 */
@Service
@RequiredArgsConstructor
public class LogServiceImpl implements LogService {
    private final LogRepository logRepository;
    private final LogErrorMapper logErrorMapper;
    private final LogSmallMapper logSmallMapper;

    @Override
    public Object queryAll(LogQueryCriteria criteria, Pageable pageable) {
        Page<Log> page = logRepository.findAll(((root, criteriaQuery, cb) -> QueryHelp.getPredicate(root, criteria, cb)), pageable);
        String status = "ERROR";
        if (status.equals(criteria.getLogType())) {
            return PageUtil.toPage(page.map(logErrorMapper::toDto));
        }
        return page;
    }

    @Override
    public List<Log> queryAll(LogQueryCriteria criteria) {
        return logRepository.findAll(((root, criteriaQuery, cb) -> QueryHelp.getPredicate(root, criteria, cb)));
    }

    @Override
    public Object queryAllByUser(LogQueryCriteria criteria, Pageable pageable) {
        Page<Log> page = logRepository.findAll(((root, criteriaQuery, cb) -> QueryHelp.getPredicate(root, criteria, cb)), pageable);
        return PageUtil.toPage(page.map(logSmallMapper::toDto));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void save(String username, String browser, String ip, ProceedingJoinPoint joinPoint, Log log, Integer shopId) {
        if (log == null) {
            throw new IllegalArgumentException("Log 不能为 null!");
        }
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        cn.ysk.cashier.annotation.Log aopLog = method.getAnnotation(cn.ysk.cashier.annotation.Log.class);

        // 方法路径
        String methodName = joinPoint.getTarget().getClass().getName() + "." + signature.getName() + "()";
        String[] split = aopLog.value().split(":");
        if (split.length == 2) {
            String value = SpelUtil.generateKeyBySpEL(split[1], joinPoint);
            // 描述
            log.setDescription(split[0] + ":" + value);
        } else if (split.length == 3) {
//            String v1 = SpelUtil.generateKeyBySpEL(split[1], joinPoint);
            String v2 = SpelUtil.generateKeyBySpEL(split[2], joinPoint);
            if (methodName.contains("createOutAndONOperate")) {
                //subType 1入库 -1出库
                if ("purchase".equals(v2) || "purveyor".equals(v2)) {
                    v2 = "入库";
                } else {
                    v2 = "出库";
                }
            }else if(methodName.contains("stockInOut")){
                if (("in".equals(v2) || "purveyor".equals(v2))) {
                    v2 = "入库";
                } else {
                    v2 = "出库";
                }
            }
            // 描述
            log.setDescription(split[0] + ":" + v2 );
        }else {
            log.setDescription(split[0]);
        }
        log.setRequestIp(ip);
        log.setAddress(StringUtils.getCityInfo(log.getRequestIp()));
        log.setMethod(methodName);
        log.setUsername(username);
        log.setParams(getParameter(method, joinPoint.getArgs()));
        // 记录登录用户，隐藏密码信息
        if (signature.getName().equals("login") && StringUtils.isNotEmpty(log.getParams())) {
            JSONObject obj = JSONUtil.parseObj(log.getParams());
            log.setUsername(obj.getStr("username", ""));
            log.setParams(JSONUtil.toJsonStr(Dict.create().set("username", log.getUsername())));
        }
        log.setBrowser(browser);
        log.setShopId(shopId);
        logRepository.save(log);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void save(String description,String method,Integer shopId,String param) {
        Log log = new Log();
        HttpServletRequest request = RequestHolder.getHttpServletRequest();
        log.setUsername(SecurityUtils.getCurrentUsername());
        log.setLogType("INFO");
        log.setBrowser(StringUtils.getBrowser(request));
        log.setRequestIp(StringUtils.getIp(request));
        log.setAddress(StringUtils.getCityInfo(log.getRequestIp()));
        log.setShopId(shopId);

        log.setDescription(description);
        log.setMethod(method);
        log.setParams(param);
        logRepository.save(log);
    }

    /**
     * 根据方法和传入的参数获取请求参数
     */
    private String getParameter(Method method, Object[] args) {
        List<Object> argList = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            //将RequestBody注解修饰的参数作为请求参数
            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
            if (requestBody != null) {
                argList.add(args[i]);
            }
            //将RequestParam注解修饰的参数作为请求参数
            RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
            if (requestParam != null) {
                Map<String, Object> map = new HashMap<>(2);
                String key = parameters[i].getName();
                if (!StringUtils.isEmpty(requestParam.value())) {
                    key = requestParam.value();
                }
                map.put(key, args[i]);
                argList.add(map);
            }
        }
        if (argList.isEmpty()) {
            return "";
        }
        return argList.size() == 1 ? JSONUtil.toJsonStr(argList.get(0)) : JSONUtil.toJsonStr(argList);
    }

    @Override
    public Object findByErrDetail(Long id) {
        Log log = logRepository.findById(id).orElseGet(Log::new);
        ValidationUtil.isNull(log.getId(), "Log", "id", id);
        byte[] details = log.getExceptionDetail();
        return Dict.create().set("exception", new String(ObjectUtil.isNotNull(details) ? details : "".getBytes()));
    }

    @Override
    public void download(List<Log> logs, HttpServletResponse response) throws IOException {
        List<Map<String, Object>> list = new ArrayList<>();
        for (Log log : logs) {
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("用户名", log.getUsername());
            map.put("IP", log.getRequestIp());
            map.put("IP来源", log.getAddress());
            map.put("描述", log.getDescription());
            map.put("浏览器", log.getBrowser());
            map.put("请求耗时/毫秒", log.getTime());
            map.put("异常详情", new String(ObjectUtil.isNotNull(log.getExceptionDetail()) ? log.getExceptionDetail() : "".getBytes()));
            map.put("创建日期", log.getCreateTime());
            list.add(map);
        }
        FileUtil.downloadExcel(list, response);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delAllByError() {
        logRepository.deleteByLogType("ERROR");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delAllByInfo() {
        logRepository.deleteByLogType("INFO");
    }

    @Override
    public Map<String, Object> shopInfoLog(LogQueryCriteriaExt criteria, Pageable pageable) {
        Page<Log> page = logRepository.findAll(((root, criteriaQuery, cb) -> QueryHelp.getPredicate(root, criteria, cb)), pageable);
        List<LogVO> logVOList = new ArrayList<>();
        for (Log log : page.getContent()) {
            LogVO logVO = new LogVO();
            logVO.setDescription(log.getDescription());
            logVO.setCreateTime(log.getCreateTime());
            logVO.setUsername(log.getUsername());
            logVO.setRequestIp(log.getRequestIp());
            logVOList.add(logVO);
        }
        Map<String, Object> map = new LinkedHashMap<>(2);
        map.put("content", logVOList);
        map.put("totalElements", page.getTotalElements());
        return map;
    }

}
