package com.yihu.hos.crawler.storage;

import com.fasterxml.jackson.databind.node.ObjectNode;
import com.mongodb.BasicDBObject;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.model.IndexOptions;
import com.mongodb.client.model.UpdateOptions;
//import com.yihu.common.util.log.DebugLogger;
import com.yihu.hos.common.mongo.MongoDB;
import com.yihu.hos.crawler.format.DataSetTransformer;
import com.yihu.hos.crawler.format.IDataTransformer;
import com.yihu.hos.crawler.model.config.SysConfig;
import com.yihu.hos.crawler.model.patient.Patient;
import com.yihu.hos.crawler.model.patient.PatientIdentity;
import com.yihu.hos.crawler.model.patient.PatientIndex;
import com.yihu.hos.crawler.format.AdapterScheme;
import com.yihu.hos.crawler.service.PatientCDAIndex;
import com.yihu.hos.web.framework.util.file.ConfigureUtil;
import com.yihu.hos.core.file.FileUtil;
import com.yihu.hos.core.datatype.DateUtil;
import com.yihu.hos.core.datatype.NumberUtil;
import org.bson.Document;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.mongodb.client.model.Filters.and;
import static com.mongodb.client.model.Filters.eq;
import static com.mongodb.client.model.Projections.excludeId;

/**
 * 档案数据只使用Mongo进行存储
 * 目前阶段只会有两种数据类型
 * 1.结构化，数据集
 * 2.非结构化，文档（Pictures,Word,PDF,Video etc.）
 *
 * @author Air
 * @version 1.0
 * @created 2015.07.06 10:38
 */
public class MongodbStorage implements IDataStorage {
    public static final String KEY = "code";
    public static final String PATIENT_ID = "patient_id";
    public static final String EVENT_NO = "event_no";
    public static final String CREATE_AT = "create_at";
    public static final String CREATE_TIME = "create_time";
    public static final String ORG_CODE = "org_code";
    public static final String TTL_INDEX = "ceate_at_1";   //TTL index name, 过期时间索引
    public static final String TTL_INDEX_EXPIRED = "create_time_1"; //旧的TTL index name,已经作废，用于删除索引时使用。
    public static final String INNER_VERSION = "inner_version";
    public static final String EVENT_TIME = "event_time";

    protected String dbName;
    protected AdapterScheme adapterScheme;

    public MongodbStorage(AdapterScheme adapterScheme, String dbName) {
        this.adapterScheme = adapterScheme;
        this.dbName = dbName;
    }

    @Override
    public boolean save(IDataTransformer dataTransformer) {
        DataSetTransformer dataSetTransformer = (DataSetTransformer) dataTransformer;
        ObjectNode jsonObject = (ObjectNode) dataSetTransformer.getJsonObject();

        if (jsonObject != null) {
            String dataSetCode = jsonObject.get(getKey()).asText();
            String patientId = jsonObject.get(PATIENT_ID).asText();
            String eventNo = jsonObject.get(EVENT_NO).asText();

            try {
                MongoCollection<Document> collection = MongoDB.db(dbName).getCollection(dataSetCode);
                createIndex(collection);    //创建索引

                Document filter = new Document();
                filter.append(PATIENT_ID, patientId);
                filter.append(EVENT_NO, eventNo);
                collection.deleteMany(filter);
                UpdateOptions updateOptions = new UpdateOptions();
                updateOptions.upsert(true);
                collection.replaceOne(filter, Document.parse(jsonObject.toString()), updateOptions);

                String url = createUrl(dataSetCode, patientId, eventNo);
                Date expiryDate = DateUtil.setDateTime(new Date(), getExpireDays().intValue());
                SimpleDateFormat sdf = new SimpleDateFormat(DateUtil.DEFAULT_YMDHMSDATE_FORMAT);
                String date = sdf.format(expiryDate);

                Document updateDoc = new Document(CREATE_AT, new Date());
                updateDoc.put("resource.url", url);
                updateDoc.put("resource.expiry_date", date);
                collection.updateMany(filter, new Document("$set", updateDoc));

            } catch (Exception e) {
                //DebugLogger.fatal("保存病人档案信息至MongoDB异常:", e);
                return false;
            }

            return true;
        }

        return false;
    }

    /**
     * @param patient
     * @return
     * @modify 将档案生成到到data目录
     */
    @Override
    public boolean toFile(Patient patient) {
        boolean result = true;
        PatientCDAIndex patientCDAIndex = new PatientCDAIndex(patient);
        Document datasetDoc = new Document();
        Document resultDoc = new Document();
        try {
            // 生成文件,轻量模式需清空data中数据
            for (String name : MongoDB.db(dbName).listCollectionNames()) {
                MongoCollection<Document> collection = MongoDB.db(dbName).getCollection(name);
                FindIterable<Document> documents = collection.find(and(eq(PATIENT_ID, patient.getPatientId()), eq(EVENT_NO, patient.getEventNo()))).projection(excludeId());
                try (MongoCursor<Document> cursor = documents.iterator()) {
                    while (cursor.hasNext()) {
                        String filePath = patientCDAIndex.createDataIndex(dbName, PatientCDAIndex.FileType.JSON);
                        try {
                            Document doc = cursor.next();
                            if ("HDSC01_02".equals(name) || "HDSC02_09".equals(name)) {
                                resultDoc.put(PATIENT_ID, doc.get(PATIENT_ID));
                                resultDoc.put(EVENT_NO, doc.get(EVENT_NO));
                                resultDoc.put(ORG_CODE, doc.get(ORG_CODE));
                                resultDoc.put(INNER_VERSION, doc.get(INNER_VERSION));
                                resultDoc.put(EVENT_TIME, doc.get(EVENT_TIME));
                                if ("HDSC01_02".equals(name)) {
                                    resultDoc.put("visit_type", "1");
                                } else {
                                    resultDoc.put("visit_type", "2");//临时约定，后续从字典中获取
                                }
                            }
                            Map<String, String> resource = (Map<String, String>) doc.get("resource");
                            datasetDoc.put(name, "");
                            resultDoc.put("expiry_date", resource.get("expiry_date"));
                            boolean writeFile = FileUtil.writeFile(filePath, doc.toJson(), "UTF-8");
                            if (!writeFile) {
                                //DebugLogger.fatal("存储临时文件失败：" + cursor.next().toJson());
                                result = false;
                            }

                        } catch (IOException e) {
                            //DebugLogger.fatal("存储临时文件失败.", e);
                            result = false;
                        }
                    }
                } catch (Exception e) {
                    //DebugLogger.fatal("", e);
                }
            }

            //摘要信息生成
//            Document indexData = genPatientIndexData(patient);
//            if (indexData != null) {
//                resultDoc.put("dataset", datasetDoc);
//                resultDoc.put("sumary", indexData);
//                String indexPath = patientCDAIndex.createDataSetIndex("index", PatientCDAIndex.FileType.JSON);
//                boolean writeFile = FileUtil.writeFile(indexPath, resultDoc.toJson(), "UTF-8");
//                if (!writeFile) {
//                    //DebugLogger.fatal("存储索引临时文件失败：" + resultDoc.toJson());
//                    result = false;
//                }
//            }

        } catch (Exception e) {
            //DebugLogger.fatal("", e);
            result = false;
        }

        return result;
    }

    @Override
    public String getDataSet(Patient patient, String dataSetCode) {
        try {
            MongoCollection<Document> collection = MongoDB.db(dbName).getCollection(dataSetCode);
            FindIterable<Document> documents = collection.find(and(eq(PATIENT_ID, patient.getPatientId()), eq(EVENT_NO, patient.getEventNo()), eq(ORG_CODE, patient.getOrgCode()))).projection(excludeId());
            Document document = documents.first();
            if (document != null) {
                return document.toJson();
            }
        } catch (Exception e) {
            //DebugLogger.fatal("", e);
        }

        return null;
    }

    /**
     * 根据条件 获取数据集信息
     *
     * @param dataSetCode 数据集编码
     * @param params      map参数集合
     * @return
     */
    @Override
    public String getArchive(String dataSetCode, Map<String, Object> params) {
        String data = null;
        boolean result = true;
        try {
            MongoCollection<Document> collection = MongoDB.db(dbName).getCollection(dataSetCode);
            BasicDBObject basicDBObject = new BasicDBObject();
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                basicDBObject.put(entry.getKey(), entry.getValue());
            }

            FindIterable<Document> documents = collection.find(basicDBObject);
            try (MongoCursor<Document> cursor = documents.iterator()) {
                while (cursor.hasNext()) {
                    data = cursor.next().toJson();
                    //DebugLogger.fatal("存储临时文 ：" + cursor.next().toJson());
                }
            } catch (Exception e) {
                //DebugLogger.fatal("", e);
            }

        } catch (Exception e) {
            //DebugLogger.fatal("", e);
        }
        return data;
    }

    @Override
    public Boolean isStored(String orgCode, String patientID, String eventNo) {
        HashMap<String, PatientIdentity> patientIdentityHashMap = SysConfig.getInstance().getPatientIdentityHashMap();
        Set<Map.Entry<String, PatientIdentity>> entries = patientIdentityHashMap.entrySet();
        Iterator<Map.Entry<String, PatientIdentity>> iterator = entries.iterator();
        try {
            while (iterator.hasNext()) {
                Map.Entry<String, PatientIdentity> next = iterator.next();
                String datasetCode = next.getKey();
                MongoCollection<Document> collection = MongoDB.db(dbName).getCollection(datasetCode);
                Document document = new Document();
                document.append(ORG_CODE, orgCode);
                document.append(PATIENT_ID, patientID);
                document.append(EVENT_NO, eventNo);

                Document findDoc = collection.find(document).first();
                if (findDoc != null) {
                    return true;
                }
            }
        } catch (Exception e) {
            //DebugLogger.fatal("", e);
        }

        return false;
    }

    protected void createIndex(MongoCollection<Document> collection) {
        for (final Document index : collection.listIndexes()) {
            if (index.get("name").equals(TTL_INDEX_EXPIRED)) {
                collection.dropIndex(TTL_INDEX_EXPIRED);  //删除旧的TTL Index
            } else if (index.get("name").equals(TTL_INDEX)) {
                return;
            }
        }

        Document createTimeIndex = new Document(CREATE_AT, 1);
        IndexOptions indexOptions = new IndexOptions();
        indexOptions.expireAfter(getExpireDays(), TimeUnit.DAYS);
        indexOptions.name(TTL_INDEX);
        collection.createIndex(createTimeIndex, indexOptions);

        Document patientIndex = new Document();
        patientIndex.append(PATIENT_ID, 1);
        patientIndex.append(EVENT_NO, 1);
        collection.createIndex(patientIndex);
    }

    /**
     * url生成
     *
     * @param patientId 病人ID
     * @param eventNo   事件号
     * @return
     */
    protected String createUrl(String dataSetCode, String patientId, String eventNo) {
        String requestPath = ConfigureUtil.getProValue("archive.properties", "hos.archives.request.url");
        return requestPath + dataSetCode + "/" + patientId + "/" + eventNo;
    }

    protected String getKey() {
        return KEY;
    }

    protected Long getExpireDays() {
        final Long expireDay = 30L;
        String value = ConfigureUtil.getProValue("archive.properties","hos.archives.expiry.days");
        Long days = NumberUtil.toLong(value);
        return days == null ? expireDay : days;
    }


    /**
     * 病人摘要信息生成
     * 从sys.config文件中的配置读取所需的摘要信息
     *
     * @param patient
     * @return
     */
    protected Document genPatientIndexData(Patient patient) {
        Map<String, PatientIndex> patientIndexMap = SysConfig.getInstance().getPatientIndexMap();
        PatientIndex patientIndex = null;
        List<Document> arrayNode = null;
        Document objectNode = null;
        Document result = new Document();
        MongoCursor<Document> cursor = null;
        MongoCursor<Document> diagCursor = null;
        try {
            for (Map.Entry<String, PatientIndex> entry : patientIndexMap.entrySet()) {
                String dataSetCode = entry.getKey();
                patientIndex = entry.getValue();
                arrayNode = new ArrayList<>();
                MongoCollection<Document> collection = MongoDB.db(dbName).getCollection(dataSetCode);
                FindIterable<Document> documents = collection.find(and(eq(KEY, dataSetCode), eq(PATIENT_ID, patient.getPatientId()), eq(EVENT_NO, patient.getEventNo()))).projection(excludeId());
                cursor = documents.iterator();
                if (cursor.hasNext()) {
                    while (cursor.hasNext()) {
                        Document document = cursor.next();
                        List<Document> list = document.get("data", List.class);
                        for (Document doc : list) {
                            objectNode = new Document();
                            objectNode.put(patientIndex.getPatientId(), patient.getPatientId());
                            objectNode.put(patientIndex.getEventNoCode(), patient.getEventNo());
                            objectNode.put(patientIndex.getRefTimeCode(), doc.get(patientIndex.getRefTimeCode()) == null ? null : (String) doc.get(patientIndex.getRefTimeCode()));
                            objectNode.put("orgCode", patient.getOrgCode());
                            objectNode.put(patientIndex.getOfficeCode(), doc.get(patientIndex.getOfficeCode()) == null ? null : (String) doc.get(patientIndex.getOfficeCode()));
                            objectNode.put(patientIndex.getOfficeName(), doc.get(patientIndex.getOfficeName()) == null ? null : (String) doc.get(patientIndex.getOfficeName()));
                            if ("HDSC02_09".equals(dataSetCode)) {
                                objectNode.put(patientIndex.getLeaveTime(), doc.get(patientIndex.getLeaveTime()) == null ? null : (String) doc.get(patientIndex.getLeaveTime()));
                            }
                            arrayNode.add(objectNode);
                        }
                    }
                    if (arrayNode != null && arrayNode.size() > 0) {
                        result.put(dataSetCode, arrayNode);
                    } else {
                        continue;
                    }
                    String diagDataSet = patientIndex.getDiagDataSet();
                    MongoCollection<Document> diagCollection = MongoDB.db(dbName).getCollection(diagDataSet);
                    FindIterable<Document> diags = diagCollection.find(and(eq(KEY, diagDataSet), eq(PATIENT_ID, patient.getPatientId()), eq(EVENT_NO, patient.getEventNo()))).projection(excludeId());
                    diagCursor = diags.iterator();
                    arrayNode = new ArrayList<>();
                    while (diagCursor.hasNext()) {
                        Document document = diagCursor.next();
                        List<Document> list = document.get("data", List.class);
                        for (Document doc : list) {
                            objectNode = new Document();
                            objectNode.put(patientIndex.getDiagType(), doc.get(patientIndex.getDiagType()) == null ? null : (String) doc.get(patientIndex.getDiagType()));
                            objectNode.put(patientIndex.getDiagCode(), doc.get(patientIndex.getDiagCode()) == null ? null : (String) doc.get(patientIndex.getDiagCode()));
                            objectNode.put(patientIndex.getDiagName(), doc.get(patientIndex.getDiagName()) == null ? null : (String) doc.get(patientIndex.getDiagName()));
                            arrayNode.add(objectNode);
                        }
                    }
                    if (arrayNode != null && arrayNode.size() > 0) {
                        result.put(diagDataSet, arrayNode);
                    }
                } else {
                    continue;
                }
            }
            if (result == null) {
                return null;
            } else {
                return result;
            }
        } catch (Exception e) {
            //DebugLogger.fatal("", e);
        } finally {
            if (cursor != null) {
                cursor.close();
            }
            if (diagCursor != null) {
                diagCursor.close();
            }
        }
        return null;
    }

}
