package cn.lihongjie.coal.sysconfig.service;

import cn.lihongjie.coal.base.dto.CommonQuery;
import cn.lihongjie.coal.base.dto.IdRequest;
import cn.lihongjie.coal.base.service.BaseService;
import cn.lihongjie.coal.common.CollectionUtils;
import cn.lihongjie.coal.common.Constants;
import cn.lihongjie.coal.exception.BizException;
import cn.lihongjie.coal.sysconfig.dto.CreateSysConfigDto;
import cn.lihongjie.coal.sysconfig.dto.SaveAllConfigDto;
import cn.lihongjie.coal.sysconfig.dto.SysConfigDto;
import cn.lihongjie.coal.sysconfig.dto.UpdateSysConfigDto;
import cn.lihongjie.coal.sysconfig.entity.SysConfigEntity;
import cn.lihongjie.coal.sysconfig.mapper.SysConfigMapper;
import cn.lihongjie.coal.sysconfig.repository.SysConfigRepository;

import io.vavr.Tuple2;

import jakarta.annotation.PostConstruct;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationContext;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

@Service
@Slf4j
@Transactional
public class SysConfigService extends BaseService<SysConfigEntity, SysConfigRepository> {

    @Autowired SysConfigRepository repository;

    @Autowired SysConfigMapper mapper;
    @Autowired ConversionService conversionService;

    @PostConstruct
    public void init() {}

    @Autowired CacheManager cacheManager;

    public void initDefault() {

        Map<String, SysConfigEntity> all =
                StreamSupport.stream(findAll().spliterator(), false)
                        .collect(Collectors.toMap(e -> e.getCode(), e -> e));

        addDictConfig(all, Constants.SYSCONFIG_ENABLE_CAPTCHA, "验证码状态", "1", "status.type");
        addDictConfig(all, Constants.SYSCONFIG_ENABLE_REQUEST_SIGN, "请求签名验证", "0", "status.type");

        addNumberConfig(
                all,
                Constants.SYSCONFIG_SESSION_TIMEOUT,
                "登录会话超时时间（s）",
                TimeUnit.HOURS.toSeconds(1) + "",
                TimeUnit.MINUTES.toSeconds(1),
                TimeUnit.HOURS.toSeconds(24));
        addNumberConfig(all, Constants.SYSCONFIG_ACCOUNT_MAX_ONLINE, "账户同时登录人数", 1 + "", 1L, 100L);

        addDictConfig(all, Constants.SYSCONFIG_RESETPWD_ENABLE, "重置密码状态", "1", "status.type");

        addNumberConfig(
                all,
                Constants.SYSCONFIG_RESETPWD_TIMEOUT,
                "重置密码会话超时时间（s）",
                TimeUnit.MINUTES.toSeconds(10) + "",
                TimeUnit.MINUTES.toSeconds(1),
                TimeUnit.HOURS.toSeconds(24));
        addNumberConfig(
                all,
                Constants.SYSCONFIG_RESETPWD_MAX_FAIL_COUNT,
                "重置密码最多失败次数",
                3 + "",
                1L,
                Integer.MAX_VALUE);

        addNumberConfig(
                all,
                Constants.SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_MIN,
                "登录会话全局限流(每分钟)",
                120 + "",
                1L,
                Integer.MAX_VALUE);

        addNumberConfig(
                all,
                Constants.SYSCONFIG_USER_GLOBAL_RATE_LIMIT_PER_MIN,
                "用户全局限流(每分钟)",
                120 + "",
                1L,
                Integer.MAX_VALUE);

        addNumberConfig(
                all,
                Constants.SYSCONFIG_ANONYMOUS_GLOBAL_RATE_LIMIT_PER_MIN,
                "匿名访问全局限流(每分钟)",
                200 + "",
                1L,
                Integer.MAX_VALUE);

        addNumberConfig(
                all,
                Constants.SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_HOUR,
                "登录会话全局限流(每小时)",
                3600 + "",
                1L,
                Integer.MAX_VALUE);

        addNumberConfig(
                all,
                Constants.SYSCONFIG_USER_GLOBAL_RATE_LIMIT_PER_HOUR,
                "用户全局限流(每小时)",
                3600 + "",
                1L,
                Integer.MAX_VALUE);

        addNumberConfig(
                all,
                Constants.SYSCONFIG_ANONYMOUS_GLOBAL_RATE_LIMIT_PER_HOUR,
                "匿名访问全局限流(每小时)",
                3600 + "",
                1L,
                Integer.MAX_VALUE);

        addNumberConfig(
                all,
                Constants.SYSCONFIG_SESSION_GLOBAL_RATE_LIMIT_PER_DAY,
                "登录会话全局限流(每天)",
                3600 * 8 + "",
                1L,
                Integer.MAX_VALUE);

        addNumberConfig(
                all,
                Constants.SYSCONFIG_USER_GLOBAL_RATE_LIMIT_PER_DAY,
                "用户全局限流(每天)",
                3600 * 8 + "",
                1L,
                Integer.MAX_VALUE);

        addNumberConfig(
                all,
                Constants.SYSCONFIG_ANONYMOUS_GLOBAL_RATE_LIMIT_PER_DAY,
                "匿名访问全局限流(每天)",
                3600 * 8 + "",
                1L,
                Integer.MAX_VALUE);

        addDictConfig(all, Constants.SYSCONFIG_PASSWORD_DICT_DETECT, "密码字典检测", "1", "status.type");
        addNumberConfig(
                all,
                Constants.SYSCONFIG_PASSWORD_STRENGTH_MIN,
                "密码强度最小值(0不校验)",
                2 + "",
                0L,
                Integer.MAX_VALUE);

        addStringConfig(
                all,
                Constants.SYSCONFIG_REQUEST_SIGN_IGNORE_URLS,
                "接口签名忽略URL",
                "**/*/downloadBatch,**/*/downloadFileLocal,**/*/downloadFile");

        addDictConfig(all, Constants.SYSCONFIG_ENABLE_REQUEST_SUBMIT_TOKEN, "是否启用提交令牌", "1", "status.type");
    }

    private void addNumberConfig(
            Map<String, SysConfigEntity> all,
            String code,
            String name,
            String value,
            Number min,
            Number max) {
        if (!all.containsKey(code)) {
            SysConfigEntity entity = new SysConfigEntity();
            entity.setName(name);
            entity.setCode(code);
            entity.setConfigVal(value);
            entity.setDictCode(null);
            entity.setMinValue(min == null ? null : min.intValue());
            entity.setMaxValue(max == null ? null : max.intValue());
            entity.setRegexValidator(null);
            entity.setType("2");
            repository.save(entity);
        }
    }

    private void addStringConfig(
            Map<String, SysConfigEntity> all, String code, String name, String value) {
        if (!all.containsKey(code)) {
            SysConfigEntity entity = new SysConfigEntity();
            entity.setName(name);
            entity.setCode(code);
            entity.setConfigVal(value);
            entity.setDictCode(null);
            entity.setMinValue(null);
            entity.setMaxValue(null);
            entity.setRegexValidator(null);
            entity.setType("1");
            repository.save(entity);
        }
    }

    private void addDictConfig(
            Map<String, SysConfigEntity> all,
            String code,
            String name,
            String value,
            String dictCode) {
        if (!all.containsKey(code)) {
            SysConfigEntity entity = new SysConfigEntity();
            entity.setName(name);
            entity.setCode(code);
            entity.setConfigVal(value);
            entity.setDictCode(dictCode);
            entity.setMaxValue(null);
            entity.setMinValue(null);
            entity.setRegexValidator(null);
            entity.setType("3");
            repository.save(entity);
        }
    }

    @Autowired ApplicationContext applicationContext;

    @Cacheable(cacheNames = Constants.CACHE_SYSCONFIG, key = "#configKey")
    public String getConfigVal(String configKey) {

        SysConfigEntity config = this.repository.findByCode(configKey);

        if (config == null) {
            return null;
        }
        return config.getConfigVal();
    }

    public void clearCache() {
        cacheManager.getCache(Constants.CACHE_SYSCONFIG).clear();
    }

    public boolean isEnable(String configKey) {
        return StringUtils.equalsIgnoreCase(
                applicationContext.getBean(this.getClass()).getConfigVal(configKey), "1");
    }

    public void saveAllConfig(SaveAllConfigDto dto) {

        List<SysConfigEntity> all = findAll();

        List<Tuple2<SysConfigEntity, CreateSysConfigDto>> joins =
                CollectionUtils.leftHashJoin(
                        all,
                        dto.getConfigs(),
                        SysConfigEntity::getCode,
                        CreateSysConfigDto::getCode);

        joins.forEach(
                x -> {
                    if (x._2 == null) {
                        throw new BizException("缺少配置项 {}", x._1.getName());
                    }

                    x._1.validate(x._2.getConfigVal());
                    x._1.setConfigVal(x._2.getConfigVal());
                });

        this.repository.saveAll(joins.stream().map(x -> x._1).collect(Collectors.toList()));

        this.clearCache();
    }

    public SysConfigDto create(CreateSysConfigDto request) {

        SysConfigEntity entity = mapper.toEntity(request);

        this.repository.save(entity);
        this.clearCache();
        return getById(entity.getId());
    }

    public SysConfigDto update(UpdateSysConfigDto request) {
        SysConfigEntity entity = this.repository.get(request.getId());
        this.mapper.updateEntity(entity, request);

        this.repository.save(entity);

        this.clearCache();

        return getById(entity.getId());
    }

    public void delete(IdRequest request) {

        this.repository.deleteAllById(request.getIds());
    }

    public SysConfigDto getById(String id) {

        SysConfigEntity entity = repository.get(id);

        return mapper.toDto(entity);
    }

    public Page<SysConfigDto> list(CommonQuery query) {

        Page<SysConfigEntity> page =
                repository.findAll(
                        query.specification(conversionService),
                        PageRequest.of(
                                query.getPageNo(),
                                query.getPageSize(),
                                Sort.by(query.getOrders())));

        return page.map(this.mapper::toDto);
    }
}
