package org.allaymc.server.pack.loader;

import lombok.SneakyThrows;
import org.allaymc.api.pack.PackLoader;
import org.allaymc.server.pack.ResourcePackException;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.*;
import java.util.Collections;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * The loader that loads packs that are inside the jar plugins.
 *
 * @author daoge_cmd
 */
public class InPluginPackLoader implements PackLoader {

    public static final Factory FACTORY = new InPluginFactory();

    private static final String IN_PLUGIN_RESOURCE_PACK_PATH = "assets/resource_pack";
    private static final String ENCRYPTION_KEY_FILE_NAME = "encryption.key";

    private final FileSystem zipFileSystem;
    private final Path root;

    @SneakyThrows
    private InPluginPackLoader(Path path) {
        this.zipFileSystem = FileSystems.newFileSystem(URI.create("jar:" + path.toUri()), Collections.singletonMap("create", "false"));
        this.root = zipFileSystem.getPath("/").resolve(IN_PLUGIN_RESOURCE_PACK_PATH);
    }

    @Override
    public Path getLocation() {
        return this.root;
    }

    @Override
    public boolean hasFile(String name) {
        Path zipPath = root.resolve(name);
        return Files.exists(zipPath) && Files.isRegularFile(zipPath);
    }

    @Override
    public InputStream getFile(String name) {
        Path zipPath = root.resolve(name);
        try {
            return Files.exists(zipPath) ? Files.newInputStream(zipPath) : null;
        } catch (IOException e) {
            throw new ResourcePackException(e);
        }
    }

    @Override
    public byte[] readAllBytes() {
        try (var byteArrayOutputStream = new ByteArrayOutputStream();
             var zipOutputStream = new ZipOutputStream(byteArrayOutputStream);
             var stream = Files.walk(root)) {
            stream.forEach(path -> {
                if (Files.isDirectory(path)) {
                    return;
                }

                if (path.getFileName().toString().equals(ENCRYPTION_KEY_FILE_NAME) && path.getParent().equals(root)) {
                    return;
                }

                try (var in = Files.newInputStream(path)) {
                    String relative = root.relativize(path).toString().replace("\\", "/");
                    zipOutputStream.putNextEntry(new ZipEntry(relative));
                    in.transferTo(zipOutputStream);
                    zipOutputStream.closeEntry();
                } catch (IOException e) {
                    throw new ResourcePackException(e);
                }
            });
            zipOutputStream.finish();
            return byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            throw new ResourcePackException(e);
        }
    }

    @SneakyThrows
    @Override
    public String getContentKey() {
        // Pack encrypting is not supported in in-plugin packs
        var keyFilePath = root.resolve(ENCRYPTION_KEY_FILE_NAME);
        return Files.exists(keyFilePath) ? Files.readString(keyFilePath) : "";
    }

    @Override
    public void close() throws Exception {
        this.zipFileSystem.close();
    }

    private static class InPluginFactory implements Factory {

        protected static final PathMatcher PATH_MATCHER = FileSystems.getDefault().getPathMatcher("glob:**.jar");

        @Override
        public boolean canLoad(Path path) {
            if (!PATH_MATCHER.matches(path) || !Files.isRegularFile(path)) {
                return false;
            }

            // Use FileSystem instead of ZipFile to check internal entry
            URI uri = URI.create("jar:" + path.toUri());

            try (FileSystem fs = FileSystems.newFileSystem(uri, Collections.singletonMap("create", "false"))) {
                Path internalPath = fs.getPath("/").resolve(IN_PLUGIN_RESOURCE_PACK_PATH);
                return Files.exists(internalPath) && Files.isDirectory(internalPath);
            } catch (IOException e) {
                return false;
            }
        }

        @Override
        public PackLoader create(Path path) {
            return new InPluginPackLoader(path);
        }
    }
}
