package link.at17.mid.tushare.web.controller;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import jakarta.validation.UnexpectedTypeException;
import link.at17.mid.tushare.dto.R;
import link.at17.mid.tushare.interfaces.IConfig;
import link.at17.mid.tushare.service.ConfigService;
import link.at17.mid.tushare.web.exception.PageNotFoundException;
import lombok.extern.slf4j.Slf4j;

@Controller
@RequestMapping("/admin/manage/reviews")
@Slf4j
public class ConfigController extends BaseController {

    @Autowired
    Validator validator;
    
    @Autowired
    ConfigService configService;
    
    @GetMapping("/set/system/{configField}.html")
    public String configPage(@PathVariable String configField) {
        if (configField == null || configField.contains(".") || configField.contains("/")) {
            throw new PageNotFoundException();
        }
        return "admin/manage/reviews/set/system/" + configField;
    }
    
    /**
     * 保存配置项统一接口
     * <p>
     * 注意该接口和系统初始化配置接口不一样，虽然从 json 中恢复配置,
     * 但只会替换 json 中指明的字段的值, 其他值不会进行置换
     * </p>
     * @param configField 注解 @ConfigInfo 的 field
     * @param config 配置 json
     */
    @PostMapping("/set/system/{configField}.html")
    @ResponseBody
    public <T extends IConfig<T>> R<?> saveConfig(@PathVariable String configField, @Validated @RequestBody JsonNode config) throws Exception {
        Class<T> clazz = configService.getConfigClassByField(configField);
        if (clazz == null) {
            throw new PageNotFoundException();
        }
        return R.judgeThrow(() -> {
            // 找出其中有的字段
            Set<String> jsonFields = new HashSet<>();
            config.fieldNames().forEachRemaining(jsonFields::add);
            
            T newConfig = (T)new ObjectMapper().treeToValue(config, clazz);
            T oldConfig = configService.getConfig(clazz);
            // 把除了本次更新以外的字段复制过去
            BeanUtils.copyProperties(oldConfig, newConfig, ArrayUtils.toStringArray(jsonFields.toArray()));
            
            Method method = this.getClass().getMethod("saveConfig", String.class, JsonNode.class);
            MethodParameter methodParameter = new MethodParameter(method, 1);

            // 执行校验
            if (validator.supports(clazz)) {
                try {
                    BindingResult bindingResult = new BeanPropertyBindingResult(newConfig, clazz.getSimpleName());
                    validator.validate(newConfig, bindingResult);
    
                    if (bindingResult.hasErrors()) {
                        throw new MethodArgumentNotValidException(methodParameter, bindingResult);
                    }
                }
                catch (UnexpectedTypeException e) {
                    // 对指定类型未找到 validator，忽略之
                    log.debug("Cannot find a validator for {}", clazz.getName());
                }
            }

            return newConfig.saveOrUpdate();
        });
    }

}
