package com.qunar.cm.ic.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.fge.jackson.JsonLoader;
import com.github.fge.jsonschema.core.exceptions.ProcessingException;
import com.github.fge.jsonschema.core.report.ProcessingReport;
import com.github.fge.jsonschema.main.JsonSchema;
import com.github.fge.jsonschema.main.JsonSchemaFactory;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.qunar.cm.ic.common.exception.ExceptionEnum;
import com.qunar.cm.ic.common.exception.ICException;
import com.qunar.cm.ic.dao.TypeRepository;
import com.qunar.cm.ic.model.Event;
import com.qunar.cm.ic.model.Type;
import com.qunar.cm.ic.service.PropertyService;
import com.qunar.cm.ic.service.TypeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * Created by dandan.sha on 2018/08/29.
 */
@Service
public class TypeServiceImpl implements TypeService {
    private static Logger logger = LoggerFactory.getLogger(TypeServiceImpl.class);
    private static ObjectMapper objectMapper = new ObjectMapper();
    private static ObjectMapper schemaObjectMapper = new ObjectMapper().addMixIn(Type.class, Type.SchemaMixIn.class);
    private static JsonSchemaFactory factory = JsonSchemaFactory.byDefault();
    private static final String CACHE_KEY = "cache.schema";
    private static final String BASE_TYPE_FILE_NAME = "base-type.json";
    private Type baseType;

    @Resource
    private TypeRepository typeRepository;
    @Resource
    private PropertyService propertyService;

    private volatile Map<String, JsonSchema> caches;

    public TypeServiceImpl() {
        initializeBaseType();
    }

    private void initializeBaseType() {
        URL resource = getClass().getClassLoader().getResource(BASE_TYPE_FILE_NAME);
        Preconditions.checkNotNull(resource, BASE_TYPE_FILE_NAME + "文件不存在");
        try {
            baseType = objectMapper.readValue(resource, Type.class);
        } catch (IOException e) {
            throw new ICException("从" + BASE_TYPE_FILE_NAME + "中创建BaseType异常", e);
        }
    }

    @Override
    public void checkEvent(Event event) {
        JsonNode jsonNode;
        try {
            jsonNode = JsonLoader.fromString(objectMapper.writeValueAsString(event.getBody()));
        } catch (IOException e) {
            throw new ICException("序列化或反序列化Json失败", e);
        }
        Preconditions.checkNotNull(caches, "JsonSchemaCache尚未初始化完成");
        JsonSchema schema;
        schema = caches.get(event.getType());
        if (schema == null) {
            throw new ICException("事件" + event.getType() + "未定义");
        }
        ProcessingReport report;
        try {
            report = schema.validate(jsonNode);
        } catch (ProcessingException e) {
            throw new ICException("校验Json格式异常", e);
        }
        if (!report.isSuccess()) {
            throw new ICException(report.toString());
        }

    }

    @Override
    public List<Type> allTypes() {
        return mergeAllWithBaseType(typeRepository.findAll());
    }

    private List<Type> mergeAllWithBaseType(List<Type> types) {
        return types.stream().map(this::mergeWithBaseType).collect(Collectors.toList());
    }

    private Type mergeWithBaseType(Type type) {
        List<String> newRequired = Lists.newArrayList();
        newRequired.addAll(baseType.getRequired());
        newRequired.addAll(type.getRequired());
        type.setRequired(newRequired.stream().distinct().collect(Collectors.toList()));

        type.setAdditionalProperties(type.getAdditionalProperties() == null ?
                baseType.getAdditionalProperties() : type.getAdditionalProperties());

        Map<String, Object> newProperties = Maps.newLinkedHashMap(baseType.getProperties());
        newProperties.putAll(type.getProperties());
        type.setProperties(newProperties);
        return type;
    }


    @Override
    public Type getType(String name) {
        Optional<Type> optionalType = typeRepository.findOneByName(name);
        return mergeWithBaseType(optionalType.orElseThrow(() -> new ICException(ExceptionEnum.PARAMS_INVALID, "不存在事件类型:" + name)));
    }


    @Scheduled(fixedDelay = 5000L)
    public synchronized void refreshOnChanged() {
        if (propertyService.changedSinceLastAccess(CACHE_KEY)) {
            refresh();
        }
    }

    private synchronized void refresh() {
        List<Type> typeList = mergeAllWithBaseType(typeRepository.findAll());
        Map<String, JsonSchema> newCaches = Maps.newHashMap();
        typeList.forEach(type -> newCaches.put(type.getName(), createJsonSchema(type)));
        caches = newCaches;
        logger.info("更新Schema列表成功,更新的Schema数量为{}", typeList.size());
    }

    /**
     * 创建JsonSchema对象
     */
    private JsonSchema createJsonSchema(Type type) {
        String typeJson;
        try {
            typeJson = schemaObjectMapper.writeValueAsString(type);
        } catch (JsonProcessingException e) {
            throw new ICException(ExceptionEnum.DATA_CONVERTER_ERROR, "将对象转化成Json字符串失败:" + type, e);
        }
        JsonNode jsonNode;
        try {
            jsonNode = JsonLoader.fromString(typeJson);
        } catch (IOException e) {
            throw new ICException(ExceptionEnum.DATA_CONVERTER_ERROR, "从字符串中生成Json失败:" + typeJson, e);
        }
        try {
            return factory.getJsonSchema(jsonNode);
        } catch (ProcessingException e) {
            throw new ICException(ExceptionEnum.DATA_CONVERTER_ERROR, "创建JsonSchema失败:" + jsonNode, e);
        }
    }

}