package com.friday.dynamic.core;

import com.google.common.collect.Lists;
import com.friday.base.common.response.DubboResult;
import com.friday.operation.api.executor.dto.module.ModuleDto;
import com.friday.operation.api.executor.service.IBizExecuteHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

@Slf4j
@Service
public class ModuleService {

    @Autowired
    private ModuleLoader moduleLoader;
    @Autowired
    private ModuleManager moduleManager;
    @Value("${spring.profiles.active}")
    private String env;


    /**
     * 加载一个外部扩展jar，包括spring mvc接口资源，mybatis的@mapper和mapper.xml和spring bean等资源
     */
    public void manualLoadAndRegister(String jarFilePath) {
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(jarFilePath), "*.jar")) {
            stream.forEach(this::loadAndRegister);
        } catch (Exception e) {
            log.error("Load exist jar exception", e);
            throw new ModuleRuntimeException("load exist jar exception");
        }
    }

    public ModuleDto loadAndRegister(Path jarPath) {
        List<Path> dependencyJarPath = Lists.newArrayList();
        if(!"local".equals(env)){
            try {
                Path dependencyFilePath = Paths.get(jarPath.getParent().toString(), "libs");
                if(dependencyFilePath.toFile().exists()) {
                	DirectoryStream<Path> stream = Files.newDirectoryStream(dependencyFilePath, "*.jar");
                	stream.forEach(dependencyJarPath::add);
                }
            } catch (IOException e) {
                log.error("Load dependency jar exception", e);
                throw new ModuleRuntimeException("load dependency jar exception");
            }
        }
        Module module = moduleLoader.load(jarPath, dependencyJarPath);
        Module oldModule = moduleManager.register(module);
        destroyModule(oldModule);
        return ModuleDto.builder()
                .name(module.getModuleConfig().name())
                .version(module.getModuleConfig().version())
                .desc(module.getModuleConfig().desc())
                .jarPath(module.getJarPath().toString())
                .build();
    }

    public void removeAndDestroy(String moduleName, String moduleVersion) {
        Module module = moduleManager.remove(moduleName, moduleVersion);
        destroyModule(module);
    }

    private void destroyModule(Module module) {
        if (Objects.nonNull(module)) {
            try {
                module.destroy();
            } catch (Exception e) {
                log.error(String.format("Failed to destroy module: name=%s, version=%s", module.getModuleConfig().name(), module.getModuleConfig().version()), e);
                throw new ModuleRuntimeException("destroy module exception");
            }
        }
    }

    public DubboResult handle(String moduleName, String moduleVersion, String handlerName, String methodName, Object handlerArgs) {
        Module module = moduleManager.find(moduleName, moduleVersion);
        if (Objects.isNull(module)) {
            throw new ModuleRuntimeException("module not exist");
        }
        return module.doHandler(handlerName, handlerArgs, methodName);
    }

    public List<ModuleDto> queryAllModule() {
        return moduleManager.allModules().stream().map(module -> {
            ModuleConfig moduleConfig = module.getModuleConfig();
            Map<String, IBizExecuteHandler> bizHandlers = module.getBizHandlers();
            List<String> handlers = bizHandlers.values().stream().map(a -> a.getClass().getSimpleName()).collect(Collectors.toList());
            return new ModuleDto(moduleConfig.name(), moduleConfig.version(), moduleConfig.desc(), module.getJarPath().toString(), handlers);
        }).collect(Collectors.toList());
    }

}
