package com.friday.dynamic.core;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.friday.base.common.response.DubboResult;
import com.friday.operation.api.distributor.bean.BaseTask;
import com.friday.operation.api.distributor.dto.*;
import com.friday.operation.api.executor.service.IBizExecuteHandler;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.CachedIntrospectionResults;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.StringUtils;

import java.beans.Introspector;
import java.io.IOException;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.ResourceBundle;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.friday.operation.api.executor.constant.BizStrategyMethod.*;

/**
 * @Author YaoYao
 * @Date Created In 15:58 2023/2/9
 * @Description
 * @Modified By
 */
@Data
@Slf4j
@AllArgsConstructor
public class Module {
    private Path jarPath;
    private ModuleConfig moduleConfig;
    private ModuleApplicationContext moduleApplicationContext;
    private final Map<String, IBizExecuteHandler> bizHandlers;

    public Module(Path jarPath, ModuleConfig moduleConfig, ModuleApplicationContext moduleApplicationContext, URLClassLoader classLoader) {
        this.jarPath = jarPath;
        this.moduleConfig = moduleConfig;
        this.moduleApplicationContext = moduleApplicationContext;
        this.bizHandlers = scanHandlers();
    }

    private Map<String, IBizExecuteHandler> scanHandlers() {
        Map<String, IBizExecuteHandler> handlers = Maps.newHashMap();
        // find Handler in module
        Map<String, IBizExecuteHandler> beansMap = moduleApplicationContext.getBeansOfType(IBizExecuteHandler.class);
        for (String handlerName : beansMap.keySet()) {
            IBizExecuteHandler handler = beansMap.get(handlerName);
            if (!StringUtils.hasText(handlerName)) {
                throw new ModuleRuntimeException("scanHandlers handlerName is null");
            }
            checkState(!handlers.containsKey(handlerName), "Duplicated handler %s found by: %s",
                    IBizExecuteHandler.class.getSimpleName(), handlerName);
            handlers.put(handlerName, handler);
        }
        if (log.isInfoEnabled()) {
            log.info("Scan handlers finish: {}", String.join(",", handlers.keySet()));
        }
        return ImmutableMap.copyOf(handlers);
    }

    public IBizExecuteHandler getHandler(String handlerName) {
        checkNotNull(handlerName, "handlerName is null");
        IBizExecuteHandler handler = bizHandlers.get(handlerName);
        checkNotNull(handler, String.format("find handler is null, handlerName=%s", handlerName));
        return handler;
    }

    public DubboResult doHandler(String handlerName, Object handlerArgs, String methodName) {
        checkNotNull(handlerName, "handlerName is null");
        checkNotNull(handlerArgs, "handlerArgs is null");
        return doHandlerWithinModuleClassLoader(getHandler(handlerName), handlerName, handlerArgs, methodName);
    }

    private DubboResult doHandlerWithinModuleClassLoader(IBizExecuteHandler handler, String handlerName, Object handlerArgs, String methodName) {
        checkNotNull(handler, "handler is null");
        checkNotNull(handlerArgs, "handlerArgs is null");
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        try {
            ClassLoader moduleClassLoader = moduleApplicationContext.getClassLoader();
            Thread.currentThread().setContextClassLoader(moduleClassLoader);
            if(StringUtils.isEmpty(methodName)){
                throw new ModuleRuntimeException("empty method name");
            }
            return doHandlerMethod(handler, handlerArgs, methodName);
        } catch (Exception e) {
            log.error(String.format("Invoke module exception, handler=%s", handlerName), e);
            throw new ModuleRuntimeException(String.format("doHandlerWithinModuleClassLoader has error, handler=%s", handler.getClass().getName()), e);
        } finally {
            Thread.currentThread().setContextClassLoader(classLoader);
        }
    }

    private DubboResult doHandlerMethod(IBizExecuteHandler handler, Object handlerArgs, String methodName){
        if(CHECK_PARAM.getMethodName().equals(methodName)){
            return handler.checkParam((StartTaskReq) handlerArgs);
        }
        if(CREATE_JOB.getMethodName().equals(methodName)){
            return handler.createJobs((BaseTask) handlerArgs);
        }
        if(EXECUTE_JOB.getMethodName().equals(methodName)){
            return handler.executeJob((StandardJobInput) handlerArgs);
        }
        if(ON_JOB_STATE_CHANGE.getMethodName().equals(methodName)){
            return handler.onJobStateChange((TaieJobStateChangeEvent) handlerArgs);
        }
        if(ON_TASK_STATE_CHANGE.getMethodName().equals(methodName)){
            return handler.onTaskStateChange((TaieTaskStateChangeEvent) handlerArgs);
        }
        if(GET_TASK_RESULT.getMethodName().equals(methodName)){
            return handler.getTaskResult((TaskResultFilter) handlerArgs);
        }
        if(GET_DATA.getMethodName().equals(methodName)){
            return handler.getData((DataRequest) handlerArgs);
        }
        if(SYNC_DATA.getMethodName().equals(methodName)){
            return handler.syncData((DataRequest) handlerArgs);
        }
        if(HANDLE_FILE.getMethodName().equals(methodName)){
            return handler.handleFile((HandleFileDto) handlerArgs);
        }
        return DubboResult.newFailure(DubboResult.SERVER_EXCEPTION, "unsupported invoke method:" + methodName);
    }


    public void destroy() throws Exception {
        if (log.isInfoEnabled()) {
            log.info("Destroy module: name={}, version={}", moduleConfig.name(), moduleConfig.version());
        }
        // close spring context
        closeApplicationContext(moduleApplicationContext);
        // clean class loader
        clearClassLoader(moduleApplicationContext.getClassLoader());
        // delete jar file
//        Files.deleteIfExists(jarPath);
    }

    private void closeApplicationContext(ConfigurableApplicationContext applicationContext) {
        checkNotNull(applicationContext, "applicationContext is null");
        try {
            applicationContext.close();
        } catch (Exception e) {
            log.error("Failed to close application context", e);
        }
    }

    private void clearClassLoader(ClassLoader classLoader) throws IOException {
        checkNotNull(classLoader, "classLoader is null");
        // Introspector缓存BeanInfo类来获得更好的性能。卸载时刷新所有Introspector的内部缓存。
        Introspector.flushCaches();
        // 从已经使用给定类加载器加载的缓存中移除所有资源包
        ResourceBundle.clearCache(classLoader);
        // clear the introspection cache for the given ClassLoader
        CachedIntrospectionResults.clearClassLoader(classLoader);
        // close
        if (classLoader instanceof URLClassLoader) {
            ((URLClassLoader) classLoader).close();
        }
    }


}
