package me.qwq.doghouse.controller.admin;

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

import jakarta.validation.UnexpectedTypeException;
import lombok.extern.slf4j.Slf4j;
import me.qwq.doghouse.annotation.ConfigInfo;
import me.qwq.doghouse.controller.common.BaseController;
import me.qwq.doghouse.exception.PageNotFoundException;
import me.qwq.doghouse.interfaces.IConfig;
import me.qwq.doghouse.pojo.dto.R;
import me.qwq.doghouse.service.ConfigService;

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.Validator;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.BindingResult;


/**
 * 配置项控制器
 * @author Doghole
 */
@Controller
@RequestMapping("/admin/v2/config")
@Slf4j
public class ConfigController extends BaseController {

    @Autowired
    Validator validator;
    
    @Autowired
    ConfigService configService;
    
    /**
     * 前端设置页面接口，其设置项必须实现 ConfigInterface 接口，且 resources/templates/admin/config 下有对应的 html 模板，例：
     * <ol>
     * <li>假设 <code>ConfigInfo</code> 注解 field 为 wiki，则其 html 模板路径为 resources/templates/admin/config/wiki/index(.html)</li>
     * <li>假设 <code>ConfigInfo</code> 注解 field 为 mail，则其 html 模板路径为 resources/templates/admin/config/mail/index(.html)</li>
     * <li>假设 <code>ConfigInfo</code> 注解 field 为空，配置类名为 SiteConfig，则其 html 模板路径为 resources/templates/admin/config/site/index(.html)</li>
     * </ol>
     * @param configField
     * @return
     */
    @GetMapping({"/{configField}", "/{configField}/", "/{configField}/index"})
    public String configPage(@PathVariable String configField)
    {
        
        ConfigInfo info = configService.getConfigInfoByField(configField);
        if (info == null || !info.managed()) {
            log.info("Try to enter management of {} but ConfigInfo is null or not managed", configField);
            throw new PageNotFoundException();
        }
        
        request.setAttribute("title", info.name());
        return "admin/config/" + configField + "/index";
    }

    /**
     * 保存配置项统一接口
     * <p>
     * 注意该接口和系统初始化配置接口不一样，虽然从 json 中恢复配置,
     * 但只会替换 json 中指明的字段的值, 其他值不会进行置换
     * </p>
     * @param configField 注解 @ConfigInfo 的 field
     * @param config 配置 json
     */
    @PostMapping({"/{configField}", "/{configField}/", "/{configField}/index"})
    @ResponseBody
    public <T extends IConfig<T>> R<?> saveConfig(@PathVariable String configField, @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();
        });
    }

}
