package com.axis.innovators.box.plugins;

import com.axis.innovators.box.AxisInnovatorsBox;
import com.axis.innovators.box.register.LanguageManager;
import com.axis.innovators.box.register.RegistrationTool;
import com.axis.innovators.box.tools.FolderCreator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.*;
import java.lang.reflect.Field;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.jar.*;

/**
 * 插件加载器
 * @author tzdwindows 7
 */
public class PluginLoader {
    private static Logger logger;
    public static final String PLUGIN_PATH = FolderCreator.getPluginFolder();
    private static final List<PluginDescriptor> loadedPlugins = new ArrayList<>();
    private static final List<IClassTransformer> transformers = new ArrayList<>();
    private static final List<String> corePluginMainClass = new ArrayList<>();
    private static final List<String> pluginRegisteredName = new ArrayList<>();
    public static void loadPlugins() throws IOException, PluginLoadingError {
        logger = LogManager.getLogger(PluginLoader.class);
        File pluginDir = null;
        if (PLUGIN_PATH != null) {
            pluginDir = new File(PLUGIN_PATH);
        }
        File[] jars = null;
        if (pluginDir != null) {
            jars = pluginDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".jar"));
        }

        if (jars == null) {
            return;
        }
        for (int i = 0; i < jars.length; i++) {
            processJarFile(jars[i],false);
            AxisInnovatorsBox.getMain().progressBarManager.updateSubProgress(
                    "Loading Plugin " + i,
                    i,
                    jars.length);
        }
    }

    private static void processJarFile(File jarFile, boolean isCorePlugin) throws IOException, PluginLoadingError {
        try (JarFile jar = new JarFile(jarFile)) {
            // Check for CorePlugin in MANIFEST.MF
            if (isCorePlugin) {
                Attributes attributes = jar.getManifest().getMainAttributes();
                String corePluginClass = attributes.getValue("CorePlugin");
                if (corePluginClass != null) {
                    processCorePlugin(jarFile, corePluginClass);
                }
            } else {
                JarEntry pluginFile = jar.getJarEntry("plug-in.box");
                if (pluginFile != null) {
                    processWithManifest(jar, pluginFile, jarFile);
                } else {
                    processWithAnnotations(jar, jarFile);
                }
            }
        }
    }

    /**
     * 加载核心插件
     * @throws IOException 插件加载失败
     */
    public static void loadCorePlugin() throws IOException {
        File pluginDir = null;
        if (PLUGIN_PATH != null) {
            pluginDir = new File(PLUGIN_PATH);
        }
        File[] jars = null;
        if (pluginDir != null) {
            jars = pluginDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".jar"));
        }
        if (jars == null) {
            return;
        }
        for (File jar : jars) {
            try {
                processJarFile(jar, true);
            } catch (PluginLoadingError e) {
                throw new RuntimeException(e);
            }
        }
    }

    private static void processCorePlugin(File jarFile, String corePluginClass) {
        try (URLClassLoader urlClassLoader = new URLClassLoader(
                new URL[]{jarFile.toURI().toURL()},
                PluginLoader.class.getClassLoader())
        ) {
            Class<?> coreClass = urlClassLoader.loadClass(corePluginClass);
            if (LoadingCorePlugin.class.isAssignableFrom(coreClass)) {
                LoadingCorePlugin corePlugin = (LoadingCorePlugin) coreClass.getDeclaredConstructor().newInstance();
                registerTransformers(corePlugin, urlClassLoader);
            }
        } catch (Exception e) {
            logger.error("Failed to load core plugin: {}", corePluginClass, e);
        }
    }

    private static void registerTransformers(LoadingCorePlugin corePlugin, ClassLoader classLoader) {
        //if (corePlugin.getMainClass() != null) {
            corePluginMainClass.add(corePlugin.getMainClass());
            String[] transformerClasses = corePlugin.getASMTransformerClass();
            if (transformerClasses != null) {
                for (String transformerClass : transformerClasses) {
                    try {
                        Class<?> transformerClazz = classLoader.loadClass(transformerClass);
                        if (IClassTransformer.class.isAssignableFrom(transformerClazz)) {
                            IClassTransformer transformer = (IClassTransformer) transformerClazz.getDeclaredConstructor().newInstance();
                            transformers.add(transformer);
                            BoxClassLoader.addClassTransformer(transformer);
                        }
                    } catch (Exception e) {
                        logger.error("Failed to register transformer: {}", transformerClass, e);
                    }
                }
            }
        //} else {
        //    logger.error("Failed to load core plugin: {}", corePlugin.getClass().getName());
        //}
    }

    private static void processWithManifest(JarFile jar, JarEntry entry, File jarFile)
            throws IOException, PluginLoadingError {
        Properties props = new Properties();
        try (InputStream is = jar.getInputStream(entry)) {
            props.load(is);
        } catch (IOException e) {
            logger.error("Failed to load plugin from jar: {}", jarFile.getName(), e);
            return;
        }

        PluginDescriptor descriptor = new PluginDescriptor();
        descriptor.setId(props.getProperty("id"));
        descriptor.setName(props.getProperty("name"));
        descriptor.setDescription(props.getProperty("description"));
        descriptor.setIcon(props.getProperty("icon"));
        descriptor.setSupportedVersions(
                Arrays.asList(props.getProperty("supportedVersions").split(","))
        );
        String registrationName = props.getProperty("registrationName");
        verifyRegisteredNameValid(descriptor, registrationName);

        logger.info("Loaded plugin: {}", descriptor.getName());
        loadMainClass(props.getProperty("mainClass"), jarFile, descriptor);

        Attributes attributes = jar.getManifest().getMainAttributes();
        String corePluginClass = attributes.getValue("CorePlugin");
        descriptor.setTransformers(corePluginClass);
        loadPluginLanguages(jarFile, descriptor);
    }

    private static void verifyRegisteredNameValid(PluginDescriptor descriptor, String registrationName) throws PluginLoadingError {
        if (registrationName != null && !registrationName.isEmpty()
                && !pluginRegisteredName.contains(registrationName)) {
            pluginRegisteredName.add(registrationName);
        } else {
            if (registrationName == null || registrationName.isEmpty()) {
                throw new PluginLoadingError("Invalid registration name");
            }
            throw new PluginLoadingError("When the \" "
                    + descriptor.getName()
                    + "\" plugin is loaded, this plugin contains the same registered name as the previously loaded plugin");
        }

        descriptor.setRegistrationName(registrationName);
    }

    private static void processWithAnnotations(JarFile jar, File jarFile) throws PluginLoadingError {
        URLClassLoader classLoader = createClassLoader(jarFile);
        Enumeration<JarEntry> entries = jar.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            if (entry.getName().endsWith(".class")) {
                if (classLoader != null) {
                    processClassEntry(entry, jar,classLoader,jarFile);

                }
            }
        }
    }

    private static void loadPluginLanguages(File jarFile, PluginDescriptor plugin) {
        try (JarFile jar = new JarFile(jarFile)) {
            Enumeration<JarEntry> entries = jar.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                if (entry.getName().startsWith("lang/") &&
                        entry.getName().endsWith(".properties")) {
                    processLanguageFile(jar, entry, plugin);
                }
            }
        } catch (IOException e) {
            logger.error("Failed to load language files for plugin: {}", plugin.getName(), e);
        }
    }

    private static void processLanguageFile(JarFile jar, JarEntry entry, PluginDescriptor plugin) {
        String fileName = entry.getName().substring(entry.getName().lastIndexOf("/") + 1);
        String[] parts = fileName.split("[_.]");
        if (parts.length < 3) {
            return;
        }
        String langCode = parts[parts.length-1].replace(".properties", "");
        String langName = plugin.getName() + " " + langCode.toUpperCase();
        String registeredName = plugin.getRegistrationName() + "_" + langCode;
        try (InputStream is = jar.getInputStream(entry)) {
            Properties properties2 = new Properties();
            properties2.load(new InputStreamReader(is, StandardCharsets.UTF_8));
            LanguageManager.Language language = new LanguageManager.Language(
                    langName,
                    registeredName,
                    null
            ) {
                @Override
                public void loadLanguageFile(String languageFile) {
                    this.properties.putAll(properties2);
                }
            };
            LanguageManager.addLanguage(language);
            logger.info("Loaded plugin language: {} ({})", langName, registeredName);
        } catch (IOException e) {
            logger.error("Failed to load language file: {}", entry.getName(), e);
        }
    }

    private static URLClassLoader createClassLoader(File jarFile) {
        try {
            return new URLClassLoader(
                    new URL[]{jarFile.toURI().toURL()},
                    PluginLoader.class.getClassLoader()
            );
        } catch (MalformedURLException e) {
            logger.error("Error creating URLClassLoader", e);
        }
        return null;
    }

    private static void processClassEntry(JarEntry entry,JarFile jar,
                                          URLClassLoader classLoader,
                                          File jarFile) throws PluginLoadingError {
        String className = entry.getName()
                .replace("/", ".")
                .replace(".class", "");

        try {
            Class<?> clazz = classLoader.loadClass(className);
            PluginMeta meta = clazz.getAnnotation(PluginMeta.class);
            if (meta != null) {
                PluginDescriptor descriptor = new PluginDescriptor();
                descriptor.setId(meta.id());
                descriptor.setName(meta.name());
                descriptor.setDescription(meta.description());
                descriptor.setIcon(meta.icon());
                descriptor.setSupportedVersions(Arrays.asList(meta.supportedVersions()));

                String registrationName = meta.registeredName();
                verifyRegisteredNameValid(descriptor, registrationName);

                try {
                    Object instance = clazz.getDeclaredConstructor().newInstance();
                    descriptor.setInstance(instance);

                    try {
                        Field pluginInstance = instance.getClass().getDeclaredField("INSTANCE");
                        pluginInstance.setAccessible(true);
                        pluginInstance.set(null, descriptor);
                    } catch (NoSuchFieldException | IllegalArgumentException
                             | SecurityException | IllegalAccessException e) {
                        logger.warn("Failed to set plugin instance: {}", instance.getClass(), e);
                    }

                    loadedPlugins.add(descriptor);
                    Attributes attributes = jar.getManifest().getMainAttributes();
                    String corePluginClass = attributes.getValue("CorePlugin");
                    descriptor.setTransformers(corePluginClass);
                    loadPluginLanguages(jarFile, descriptor);
                    logger.info("Loaded plugin: {}", descriptor.getName());
                } catch (Exception e) {
                    logger.error("Failed to instantiate plugin class: {}", className, e);
                }
            }
        } catch (ClassNotFoundException | NoClassDefFoundError e) {
            logger.error("Error loading class: {}", className, e);
        }
    }

    private static void loadMainClass(String mainClassName, File jarFile, PluginDescriptor descriptor) {
        if (mainClassName == null || mainClassName.isEmpty()) {
            logger.error("Invalid main class name: {}", mainClassName);
            return;
        }

        try (URLClassLoader classLoader = new URLClassLoader(
                new URL[]{jarFile.toURI().toURL()},
                PluginLoader.class.getClassLoader())
        ) {
            Class<?> mainClass = classLoader.loadClass(mainClassName);
            Object instance = mainClass.getDeclaredConstructor().newInstance();
            descriptor.setInstance(instance);

            try {
                Field pluginInstance = mainClass.getDeclaredField("INSTANCE");
                pluginInstance.setAccessible(true);
                pluginInstance.set(null, descriptor);
            } catch (NoSuchFieldException | IllegalArgumentException
                     | SecurityException | IllegalAccessException e) {
                logger.warn("Failed to set plugin instance: {}", mainClassName, e);
            }
        } catch (Exception e) {
            logger.error("Failed to load main class: {}", mainClassName, e);
        }
    }

    public static List<PluginDescriptor> getLoadedPlugins() {
        return Collections.unmodifiableList(loadedPlugins);
    }
}