package cn.ibizlab.core.data.model;

import cn.ibizlab.util.errors.BadRequestAlertException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.*;

@Getter
@Setter
@NoArgsConstructor
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonIgnoreProperties(ignoreUnknown = true)
public class PojoSchema {



    @JSONField(ordinal = 1)
    private String id;

    @JSONField(ordinal = 2)
    private String type;


    @JSONField(serialize = false)
    @JsonIgnore
    private String name;

    @JSONField(serialize = false)
    @JsonIgnore
    public String getName()
    {
        if(name==null)
            name=this.getOptions()==null?"":this.getOptions().getName();
        return name;
    }

    public PojoSchema setName(String name)
    {
        if(!StringUtils.isEmpty(name))
        {
            this.name=name;
            if(options==null)
                options=new PojoOption();
            options.setName(name);
        }
        return this;
    }

    @JSONField(serialize = false)
    @JsonIgnore
    public String getSystem()
    {
        return this.getOptions()==null?"":this.getOptions().getSystem();
    }

    @JSONField(serialize = false)
    @JsonIgnore
    public String getDefaultDataSoruce()
    {
        return this.getOptions()==null?"":this.getOptions().getDefaultDataSoruce();
    }

    @JSONField(serialize = false)
    @JsonIgnore
    public String getDefaultTableName()
    {
        return this.getOptions()==null?"":this.getOptions().getTableName();
    }

    @JSONField(serialize = false)
    @JsonIgnore
    public String getPackage()
    {
        return this.getOptions()==null?"":this.getOptions().getSystem();
    }



    @JSONField(ordinal = 3)
    private String title;

    @JSONField(ordinal = 4)
    private String description;

    @JSONField(ordinal = 5)
    private Integer propertyOrder;

    @JSONField(ordinal = 6)
    private PojoOption options;

    public PojoSchema setOptions(PojoOption options)
    {
        if(options==null)
            return this;

        if(this.options!=null)
            options.putAll(this.options);
        this.options=options;
        return this;
    }

    @JSONField(ordinal = 7)
    private String ref;

    @JSONField(serialize = false)
    @JsonIgnore
    public PojoSchema copy()
    {
        return JSONObject.parseObject(JSON.toJSONString(this),PojoSchema.class);
    }

    @JSONField(serialize = false)
    @JsonIgnore
    private boolean built = false;

    @JSONField(serialize = false)
    @JsonIgnore
    public PojoSchema build()
    {
        if((!built)&&Type.object.getCode().equalsIgnoreCase(this.getType()))
        {
            if(properties!=null)
            {
                properties.values().forEach(prop->{

                    PojoSchema item=null;
                    if(Type.object.getCode().equalsIgnoreCase(prop.getType())&&(!StringUtils.isEmpty(prop.getRef())))
                        item=prop;
                    else if(Type.array.getCode().equalsIgnoreCase(prop.getType())
                            &&prop.getItems()!=null&&Type.object.getCode().equalsIgnoreCase(prop.getItems().getType())
                            &&(!StringUtils.isEmpty(prop.getItems().getRef())))
                        item=prop.getItems();
                    else
                        return;



                });
            }
            built=true;
        }
        return this;
    }

    /// object

    @JSONField(ordinal = 11)
    private Map<String,PojoSchema> properties;

    public PojoSchema setProperties(Map<String,PojoSchema> properties)
    {
        if(properties!=null)
        {
            properties.keySet().forEach(key->{
                properties.get(key).setName(key).setOwner(this);
            });
            this.properties=properties;
        }
        return this;
    }

    public Map<String,PojoSchema> getProperties()
    {
        if(!Type.object.getCode().equals(this.type))
        {
            properties=null;
            return properties;
        }
        if(properties==null)
            properties = new LinkedHashMap<>();
        return properties;
    }

    public PojoSchema addProperty(String name,PojoSchema property)
    {
        if(StringUtils.isEmpty(name))
            return this;
        Map<String,PojoSchema> props = this.getProperties();
        if(props!=null) {
            props.put(name, property.setName(name).setOwner(this));
        }
        return this;
    }

    @JSONField(serialize = false)
    @JsonIgnore
    private PojoSchema owner;

    @JSONField(ordinal = 12)
    private Set<String> required;

    public Set<String> getRequired()
    {
        if(!Type.object.getCode().equals(this.type))
        {
            required=null;
            return required;
        }
        if(required==null&&properties!=null)
        {
            required=new LinkedHashSet<>();
            properties.values().forEach(prop->{
                if (prop.getOptions()!=null&&(!prop.getOptions().isNullable()))
                    required.add(prop.getName());
            });
        }
        return required;
    }

    @JSONField(ordinal = 13)
    private Integer minProperties;

    @JSONField(ordinal = 14)
    private Integer maxProperties;


    @JsonIgnore
    @JSONField(serialize = false)
    private Map<String,PojoSchema> keyMap;

    @JsonIgnore
    @JSONField(serialize = false)
    public synchronized Map<String,PojoSchema> getKeyMap()
    {
        if(Type.object.getCode().equals(this.type))
        {
            if(keyMap==null)
            {
                keyMap=new LinkedHashMap<>();
                getProperties().values().forEach(sub->{
                    if(sub.getOptions().isKeyField()&&sub.getOptions().isPhysicalField())
                        keyMap.put(sub.getOptions().getFieldName(),sub);
                });
                if(keyMap.isEmpty())
                {
                    getProperties().values().forEach(sub->{
                        if(sub.getOptions().isUnionKeyField()&&sub.getOptions().isPhysicalField())
                            keyMap.put(sub.getOptions().getFieldName(),sub);
                    });
                }
            }

        }
        return keyMap;
    }


    ////  array

    @JSONField(ordinal = 21)
    private PojoSchema items;

    @JSONField(ordinal = 22)
    private Integer minItems;

    @JSONField(ordinal = 23)
    private Boolean uniqueItems;



    ///  String

    @JSONField(ordinal = 33)
    private Integer minLength;

    @JSONField(ordinal = 34)
    private Integer maxLength;

    @JSONField(ordinal = 35)
    private String pattern;

    @JSONField(ordinal = 36)
    private String format;

    ///  integer number

    @JSONField(ordinal = 41)
    private BigDecimal minimum;

    @JSONField(ordinal = 42)
    private BigDecimal maximum;

    @JSONField(ordinal = 43)
    private Boolean exclusiveMinimum;

    @JSONField(ordinal = 44)
    private Boolean exclusiveMaximum;

    @JSONField(ordinal = 45)
    private Integer multipleOf;


    public PojoSchema writeTo(Path path)
    {
        return writeTo(this,path);
    }

    public static PojoSchema fromPath(Path path)
    {
        try {
            if(!Files.exists(path))
                throw new BadRequestAlertException("读取文件失败","PojoSchema",path.toString());
            return JSON.parseObject(Files.readAllBytes(path),PojoSchema.class);

        } catch (Exception e) {
            throw new BadRequestAlertException("解析文件失败","PojoSchema",path.toString());
        }
    }

    public static PojoSchema writeTo(PojoSchema pojoSchema,Path path)
    {
        try {
            File dir=path.getParent().toFile();
            if(!dir.exists())
                dir.mkdirs();

            Files.write(path, JSON.toJSONBytes(pojoSchema), StandardOpenOption.CREATE);
        } catch (Exception e) {
            throw new BadRequestAlertException("保存文件失败","PojoSchema",path.toString());
        }
        return pojoSchema;
    }


    public static enum Type {
        string("string", "字符"),
        integer("integer", "整型"),
        number("number", "数值"),
        object("object", "对象"),
        array("array", "数组");
        public final String code;
        public final String name;

        private Type(String code, String name) {
            this.code = code;
            this.name = name;
        }

        public String getCode() {
            return code;
        }

        public String getName() {
            return name;
        }

        @Override
        public String toString() {
            return "Type{" +
                    "code='" + code + '\'' +
                    ", name='" + name + '\'' +
                    '}';
        }
    }

}
