package cn.stylefeng.team.project.modular.businessrender.sql.impl;

import cn.hutool.core.io.IoUtil;
import cn.stylefeng.team.project.modular.businessrender.mapper.BusinessRenderMapper;
import cn.stylefeng.team.project.modular.businessrender.sql.ProjectSqlExecuteApi;
import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.stereotype.Service;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

@Service
public class ProjectSqlExecuteV2Impl implements ProjectSqlExecuteApi {

    public static final Object LOCK = new Object();

    private final SqlSessionFactory sqlSessionFactory;

    public ProjectSqlExecuteV2Impl(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }

    @Override
    public List<Map<String, Object>> customFindPage(Page page, String sql, Map<String, Object> params) {
        synchronized (LOCK) {
            Long nanoTime = System.nanoTime();

            SqlSession session = null;
            try {
                session = sqlSessionFactory.openSession();

                dynamicLoadMybatisXml(MybatisXmlStructureCreator.createSelectPageXml(sql), session, nanoTime);

                BusinessRenderMapper mapper = session.getMapper(BusinessRenderMapper.class);
                return mapper.customFindPage(page, sql, params);
            } finally {
                clearDynamicMappings(session, nanoTime);
                IoUtil.close(session);
            }
        }
    }

    @Override
    public Map<String, Object> findOne(String sql, Map<String, Object> params) {
        synchronized (LOCK) {
            Long nanoTime = System.nanoTime();

            SqlSession session = null;
            try {
                session = sqlSessionFactory.openSession();

                dynamicLoadMybatisXml(MybatisXmlStructureCreator.createSelectOneXml(sql), session, nanoTime);

                BusinessRenderMapper mapper = session.getMapper(BusinessRenderMapper.class);
                return mapper.findOne(sql, params);
            } finally {
                clearDynamicMappings(session, nanoTime);
                IoUtil.close(session);
            }
        }
    }

    @Override
    public List<Map<String, Object>> findList(String sql, Map<String, Object> params) {
        synchronized (LOCK) {
            Long nanoTime = System.nanoTime();

            SqlSession session = null;
            try {
                session = sqlSessionFactory.openSession();

                dynamicLoadMybatisXml(MybatisXmlStructureCreator.createSelectListXml(sql), session, nanoTime);

                BusinessRenderMapper mapper = session.getMapper(BusinessRenderMapper.class);
                return mapper.findList(sql, params);
            } finally {
                clearDynamicMappings(session, nanoTime);
                IoUtil.close(session);
            }
        }
    }

    @Override
    public int executeDelete(String sql, Map<String, Object> params) {
        synchronized (LOCK) {
            Long nanoTime = System.nanoTime();

            SqlSession session = null;
            try {
                session = sqlSessionFactory.openSession();

                InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().dataPermission(true).build());

                dynamicLoadMybatisXml(MybatisXmlStructureCreator.createDeleteXml(sql), session, nanoTime);

                BusinessRenderMapper mapper = session.getMapper(BusinessRenderMapper.class);
                return mapper.executeDelete(sql, params);
            } finally {
                InterceptorIgnoreHelper.clearIgnoreStrategy();
                clearDynamicMappings(session, nanoTime);
                IoUtil.close(session);
            }
        }
    }

    @Override
    public int executeUpdate(String sql, Map<String, Object> params) {
        synchronized (LOCK) {
            Long nanoTime = System.nanoTime();

            SqlSession session = null;
            try {
                session = sqlSessionFactory.openSession();

                InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().dataPermission(true).build());

                dynamicLoadMybatisXml(MybatisXmlStructureCreator.createUpdateXml(sql), session, nanoTime);

                BusinessRenderMapper mapper = session.getMapper(BusinessRenderMapper.class);
                return mapper.executeUpdate(sql, params);
            } finally {
                InterceptorIgnoreHelper.clearIgnoreStrategy();
                clearDynamicMappings(session, nanoTime);
                IoUtil.close(session);
            }
        }
    }

    private static void dynamicLoadMybatisXml(String mybatisFullXml, SqlSession session, Long nanoTime) {

        String resource = "DynamicMapper_" + nanoTime + ".xml";

        Configuration configuration = session.getConfiguration();
        InputStream inputStream = null;
        try {
            inputStream = new ByteArrayInputStream(mybatisFullXml.getBytes());
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(
                    inputStream, configuration, resource, configuration.getSqlFragments());
            xmlMapperBuilder.parse();
        } finally {
            IoUtil.close(inputStream);
        }
    }

    private static void clearDynamicMappings(SqlSession session, Long nanoTime) {
        if (session == null) {
            return;
        }

        String resource = "DynamicMapper_" + nanoTime + ".xml";

        Configuration configuration = session.getConfiguration();

        Iterator<?> mappedStatementsIterator = configuration.getMappedStatements().iterator();
        while (mappedStatementsIterator.hasNext()) {
            Object ms = mappedStatementsIterator.next();
            if (ms instanceof org.apache.ibatis.mapping.MappedStatement) {
                org.apache.ibatis.mapping.MappedStatement mappedStatement = (org.apache.ibatis.mapping.MappedStatement) ms;
                if (mappedStatement.getResource() != null && mappedStatement.getResource().startsWith(resource)) {
                    mappedStatementsIterator.remove();
                }
            }
        }

        configuration.getCacheNames().removeIf(cacheName -> cacheName.startsWith(resource));

        configuration.getResultMapNames().removeIf(resultMapName -> resultMapName.startsWith(resource));

        configuration.getParameterMapNames().removeIf(parameterMapName -> parameterMapName.startsWith(resource));
    }

}
