package logwire.web.bo.session;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import logwire.core.bo.object.BizObject;
import logwire.core.exceptions.ApplicationException;
import logwire.core.meta.model.Model;
import logwire.core.support.ApplicationContextHelper;
import logwire.web.security.SecurityUtil;
import logwire.web.security.TenantUser;
import logwire.web.service.ActionContext;
import logwire.web.service.event.BuiltinEventNames;
import logwire.web.service.query.QueryService;
import logwire.web.service.query.sql.QueryTransactionCode;

import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 业务对象操作上下文会话
 */
public class Session {

/*
    private ActionContext actionContext;

    public Session(ActionContext actionContext) {
        this.actionContext = actionContext;
    }

    */
/**
     * 业务对象的缓存
     * 第一层 map 的 key 为 model 名称
     * 第二层 map 的 key 为 主键值
     *//*

    private Map<String, Map<String, BizObject>> cache = Maps.newHashMap();

    */
/**
     * 业务对象主键值的缓存
     * 第一层 map 的 key 为 model 名称
     * 第二层 map 的 key 为 model 某个字段的名称
     * 第三层 map 的 key 为 model 某个字段的值, value 为 model 主键的值
     *//*

    private Map<String, Map<String, Map<String, Set<Object>>>> pkCache = Maps.newHashMap();

    */
/**
     * 保存业务对象之间的关系
     * 第一层 map 的 key 为 明细表的 model 名称
     * 第二层 map 的 key 为 明细表的主键值
     * 第三层 map 的 value 为明细表对应的主表对应的业务对象
     * <p>
     * 保存时需要,先保存主表,再保存明细表
     *//*

    private Map<String, Map<String, Set<BizObject>>> itemHeaderRelations = Maps.newHashMap();

    */
/**
     * 保存业务对象之间的关系
     * 第一层 map 的 key 为 item 关系的主表对应的 model 名称
     * 第二层 map 的 key 为 主键值
     * 第三层 map 的 value 为 item 关系的明细表对应的业务对象
     * <p>
     * 删除时
     * item 关系时,先删除明细表,再删除主表
     *//*

    private Map<String, Map<String, Set<BizObject>>> headerItemRelations = Maps.newHashMap();

    */
/**
     * 保存业务对象之间的关系
     * 第一层 map 的 key 为 join 关系的被 join 表
     * 第二层 map 的 key 为 主键值
     * 第三层 map 的 value 为 join 关系的主表
     * <p>
     * 删除时
     * join 关系时,先删除主表,再删除被关联 JOIN 的表
     *//*

    private Map<String, Map<String, Set<BizObject>>> joinOrExtendRelations = Maps.newHashMap();

    */
/**
     * 组合对象和业务对象的映射关系
     * key 为 compositeObject.hashCode()
     *//*

    private Map<Integer, BizObject> compositeBizObjectMapping = Maps.newHashMap();

    */
/**
     * 组合对象和业务对象中的组合对象类中属性名称的映射关系
     * key 为 compositeObject.hashCode()
     *//*

    private Map<Integer, String> compositeBizObjectFieldMapping = Maps.newHashMap();

    */
/**
     * 业务对象数据的存放
     *//*

    private Map<Integer, OperableAgent> operableAgentMap = Maps.newHashMap();

    */
/**
     * 待持久化的业务对象
     *//*

    private List<BizObject> bizObjects = Lists.newArrayList();

    public ActionContext getActionContext() {
        return actionContext;
    }

    */
/**
     * 将业务对象存入上下文中
     *
     * @param t 业务对象
     *//*

    public void add(BizObject t) {
        if (t == null) {
            return;
        }
        int existIndex = -1;
        for (int i = 0; i < bizObjects.size(); i++) {
            if (t.equals(bizObjects.get(i))) {
                existIndex = i;
                break;
            }
        }
        bizObjects.add(t);
        if (existIndex != -1) {
            bizObjects.remove(existIndex);
        }
    }

    private class PersistedRecorder {
        */
/**
         * 暂存已执行持久化操作的业务对象标记
         *//*

        private Map<Integer, Boolean> record = Maps.newHashMap();

        public void accept(BizObject t) {
            record.put(t.hashCode(), true);
        }

        public boolean persistent(BizObject t) {
            return record.containsKey(t.hashCode());
        }
    }

    */
/**
     * 持久化
     *//*

    public void commit(QueryService queryService) {
        if (this.bizObjects.isEmpty()) {
            return;
        }
        PersistedRecorder persistedRecorder = new PersistedRecorder();

        //待新增的业务对象
        Map<String, List<BizObject>> insertBizObjects = Maps.newConcurrentMap();
        //非待新增的业务对象
        List<BizObject> otherBizObjects = Lists.newArrayList();

        while (!this.bizObjects.isEmpty()) {

            //将业务对象按照持久化方式分类为新增和其他
            for (int i = 0; i < this.bizObjects.size(); i++) {
                BizObject t = this.bizObjects.get(i);
                if (QueryTransactionCode.TX_INSERT.equals(t.getTxCode())) {
                    List<BizObject> list = insertBizObjects.get(t.getBoName());
                    if (list == null) {
                        list = Lists.newArrayList();
                        insertBizObjects.put(t.getBoName(), list);
                    }
                    list.add(t);
                } else {
                    otherBizObjects.add(t);
                }
            }
            this.bizObjects.clear();

            //执行新增操作
            for (Map.Entry<String, List<BizObject>> entry : insertBizObjects.entrySet()) {
                List<BizObject> bizObjects = entry.getValue();
                if (bizObjects == null) {
                    continue;
                }
                for (int i = 0; i < bizObjects.size(); i++) {
                    BizObject bizObject = bizObjects.get(i);
                    if (persistedRecorder.persistent(bizObject)) {
                        continue;
                    }
                    persistedInsert(bizObject, queryService, persistedRecorder);
                }
            }
            insertBizObjects.clear();

            //执行编辑和删除操作
            for (int i = 0; i < otherBizObjects.size(); i++) {
                BizObject bizObject = otherBizObjects.get(i);
                if (persistedRecorder.persistent(bizObject)) {
                    continue;
                }
                persisted(bizObject, queryService, persistedRecorder);
            }
            otherBizObjects.clear();
        }
        SessionHolder.clear();
    }

    private void persistedHeader(BizObject t, QueryService queryService, PersistedRecorder persistedRecorder) {
        //明细表有增删改时,头表一定要修改(如果不是新增的头表)而不管主表的数据是否有变化
        Map<String, Set<BizObject>> headers = itemHeaderRelations.get(t.getBoName());
        if (headers != null && !headers.isEmpty()) {
            Set<BizObject> set = headers.get(t.getId());
            if (set == null || set.isEmpty()) {
                return;
            }
            for (BizObject header : set) {
                if (persistedRecorder.persistent(header)) {
                    continue;
                }
                BizObjectAgent agent = getOperableAgent(t);
                agent.setForceUpdate();
                persisted(header, queryService, persistedRecorder);
            }
        }
    }

    private void persistedInsert(BizObject t, QueryService queryService, PersistedRecorder persistedRecorder) {
        BizObjectAgent agent = getOperableAgent(t);
        TenantUser currentUser = SecurityUtil.currentUser();
        if (BizObjectStatus.NEW_SAVE.equals(t.getTxCode())) {
            agent.setInternal(QueryTransactionCode.TX_FIELD_NAME, QueryTransactionCode.TX_INSERT);
//            persistedBefore(t, itemHeaderRelations, queryService); TODO 新增明细表前先新增主表
            queryService.insert(t.getBoName(), t.toMap());
            persistedRecorder.accept(t);
            persistedHeader(t, queryService, persistedRecorder);
        }
    }

    private void persisted(BizObject t, QueryService queryService, PersistedRecorder persistedRecorder) {
        BizObjectAgent agent = getOperableAgent(t);
        TenantUser currentUser = SecurityUtil.currentUser();
        if (BizObjectStatus.UPDATE.equals(t.getBizObjectStatus())) {
            agent.setInternal(QueryTransactionCode.TX_FIELD_NAME, QueryTransactionCode.TX_UPDATE);
            actionContext.emit(t.getOperableName(), BuiltinEventNames.BIZ_UPDATE_BEFORE.name(), currentUser.getDomain(), t);
            queryService.update(t.getModelName(), t.toMap());
            actionContext.emit(t.getOperableName(), BuiltinEventNames.BIZ_UPDATE_AFTER.name(), currentUser.getDomain(), t);
            persistedRecorder.accept(t);
            persistedHeader(t, queryService, persistedRecorder);
        } else if (BizObjectStatus.DELETED.equals(t.getBizObjectStatus())) {
            agent.setInternal(QueryTransactionCode.TX_FIELD_NAME, QueryTransactionCode.TX_DELETE);
            persistedBefore(t, headerItemRelations, queryService, persistedRecorder);
            persistedBefore(t, joinOrExtendRelations, queryService, persistedRecorder);
            actionContext.emit(t.getOperableName(), BuiltinEventNames.BIZ_DELETE_BEFORE.name(), currentUser.getDomain(), t);
            Model model = actionContext.model(t.getModelName());
            boolean hasVersion = model.getIncludeVersionField();
            boolean deleted;
            Object pkValue = t.getPkValue();
            if (hasVersion) {
                deleted = queryService.delete(t.getModelName(), t.getPkValue(), (int) t.get(Model.VERSION_FIELD_NAME));
            } else {
                deleted = queryService.delete(t.getModelName(), t.getPkValue());
            }
            if (!deleted) {
                throw new ApplicationException(model.getTenantProject().getMessageSourceAccessor().getMessage(
                        "failed.delete.biz-object", new Object[]{model.getVerboseNameI18n(), model.getPrimaryKeyField().getName(), pkValue},
                        String.format("删除失败:找不到或无权访问 [%s] 为 [%s] 的业务对象[%s]", model.getPrimaryKeyField().getName(), pkValue, model.getVerboseNameI18n())));
            }
            actionContext.emit(t.getOperableName(), BuiltinEventNames.BIZ_DELETE_AFTER.name(), currentUser.getDomain(), t);
            persistedRecorder.accept(t);
            persistedHeader(t, queryService, persistedRecorder);
        }
    }

    private void persistedBefore(BizObject t, Map<String, Map<String, Set<BizObject>>> relations,
                                 QueryService queryService, PersistedRecorder persistedRecorder) {
        Map<String, Set<BizObject>> map = relations.get(t.getModelName());
        if (map == null) {
            return;
        }
        Set<? extends BizObject> set = map.get(t.getPkValue());
        if (set == null || set.isEmpty()) {
            return;
        }
        for (BizObject t2 : set) {
            persistedInsert(t2, queryService, persistedRecorder);
            persisted(t2, queryService, persistedRecorder);
        }
    }

    public void flush() {
        cache.clear();
        pkCache.clear();
        itemHeaderRelations.clear();
        headerItemRelations.clear();
        joinOrExtendRelations.clear();
        compositeBizObjectMapping.clear();
        compositeBizObjectFieldMapping.clear();
        bizObjects.clear();
        operableAgentMap.clear();
    }

    public BizObject getBizObjectByCompositeObject(Object compositeObject) {
        if (compositeObject == null) {
            return null;
        }
        return compositeBizObjectMapping.get(compositeObject.hashCode());
    }

    public String getBizObjectCompositeFieldNameByCompositeObject(Object compositeObject) {
        if (compositeObject == null) {
            return null;
        }
        return compositeBizObjectFieldMapping.get(compositeObject.hashCode());
    }

    public void cacheCompositeBizObject(BizObject bizObject, Object compositeObject, String compositeName) {
        compositeBizObjectMapping.put(compositeObject.hashCode(), bizObject);
        compositeBizObjectFieldMapping.put(compositeObject.hashCode(), compositeName);
    }

    public void cacheHeaderItemRelation(BizObject header, BizObject item) {
        cacheBizObjectRelations(headerItemRelations, header, item);
        cacheBizObjectRelations(itemHeaderRelations, item, header);
    }

    public void cacheJoinOrExtendRelation(BizObject main, BizObject joinOrExtend) {
        cacheBizObjectRelations(joinOrExtendRelations, joinOrExtend, main);
    }

    private void cacheBizObjectRelations(Map<String, Map<String, Set<BizObject>>> relationsCollection, BizObject key, BizObject value) {
        Object pkValue = key.getPkValue();
        if (pkValue == null) {
            return;
        }
        Map<String, Set<BizObject>> modelMap = relationsCollection.get(key.getModelName());
        if (modelMap == null) {
            modelMap = Maps.newHashMap();
            relationsCollection.put(key.getModelName(), modelMap);
        }
        Set<BizObject> set = modelMap.get(pkValue.toString());
        if (set == null) {
            set = Sets.newHashSet();
            modelMap.put(pkValue.toString(), set);
        }
        set.add(value);
    }

    */
/**
     * 缓存根据某个字段作为查询条件加载得到的若干业务对象
     * 此处只缓存业务对象中的主键值
     * 因为 Session 是线程安全的,所以此处内部不考虑并发问题
     *
     * @param t
     * @param fieldName
     *//*

    public void cache(BizObject t, String fieldName) {
        Object pkValue = t.getId();
        if (pkValue == null) {
            return;
        }
        //拿到存放某个 model 的主键缓存值的 map
        Map<String, Map<String, Set<Object>>> modelMap = pkCache.get(t.getBoName());
        if (modelMap == null) {
            modelMap = Maps.newHashMap();
            pkCache.put(t.getBoName(), modelMap);
        }

        //拿到存放通过某个 model 的 某个字段查询得到的主键后缓存值的 map
        Map<String, Set<Object>> fieldMap = modelMap.get(fieldName);
        if (fieldMap == null) {
            fieldMap = Maps.newHashMap();
            modelMap.put(fieldName, fieldMap);
        }

        //拿到存放通过某个 model 的 某个字段查询得到的主键后缓存的主键值的列表
        Object fieldValue = t.get(fieldName);
        Set<Object> set = fieldMap.get(fieldValue.toString());
        if (set == null) {
            set = Sets.newHashSet();
            fieldMap.put(fieldValue.toString(), set);
        }
        set.add(t.getPkValue());
        fieldMap.put(fieldValue.toString(), set);
    }

    public void cacheAgent(OperableAgent agent) {
        operableAgentMap.put(agent.getTarget().hashCode(), agent);
    }

    */
/**
     * 缓存业务对象
     * 因为 Session 是线程安全的,所以此处内部不考虑并发问题
     *
     * @param t
     *//*

    public void cache(BizObject t) {
        if (t == null) {
            return;
        }
        Object pkValue = t.getPkValue();
        if (pkValue == null) {
            return;
        }
        Map<String, BizObject> map = cache.get(t.getModelName());
        if (map == null) {
            map = Maps.newHashMap();
            cache.put(t.getModelName(), map);
        }
        map.put(pkValue.toString(), t);
    }

    public void remove(BizObject t) {
        //删除缓存中的数据
        Map<String, BizObject> map = cache.get(t.getModelName());
        if (map == null) {
            return;
        }
        Object pkValue = t.getPkValue();
        if (pkValue == null) {
            return;
        }
        if (map.containsKey(pkValue.toString())) {
            map.remove(pkValue.toString());
        }
        bizObjects.remove(t);
    }

    private boolean isNew(BizObject t) {
        return BizObjectStatus.NEW.equals(t.getBizObjectStatus());
    }

    */
/**
     * 从上下文中获取之前加载过的业务对象
     *
     * @param modelName
     * @param pkValue
     * @return
     *//*

    public <T extends BizObject> T get(String modelName, Object pkValue) {
        if (modelName == null || pkValue == null) {
            return null;
        }
        Map<String, BizObject> map = cache.get(modelName);
        if (map == null) {
            return null;
        }
        return (T) map.get(pkValue.toString());
    }

    */
/**
     * 从上下文中获取之前加载过的业务对象
     *
     * @param modelName
     * @param fieldName
     * @param fieldValue
     * @return
     *//*

    public <T extends BizObject> List<T> get(String modelName, String fieldName, Object fieldValue) {
        if (modelName == null || fieldName == null || fieldValue == null) {
            return null;
        }
        Map<String, Map<String, Set<Object>>> modelMap = pkCache.get(modelName);
        if (modelMap == null || modelMap.isEmpty()) {
            return null;
        }
        Map<String, Set<Object>> fieldMap = modelMap.get(fieldName);
        if (fieldMap == null || fieldMap.isEmpty()) {
            return null;
        }
        Set pkValues = fieldMap.get(fieldValue.toString());
        if (pkValues == null || pkValues.isEmpty()) {
            return null;
        }
        List<T> result = Lists.newArrayList();
        pkValues.forEach(pkValue -> {
            T t = get(modelName, pkValue);
            if (t != null) {
                result.add(t);
            }
        });
        return result;
    }

    public <T extends Operable, R extends OperableAgent> R getOperableAgent(T operable) {
        return (R) operableAgentMap.get(operable.hashCode());
    }
*/

}