package com.mgmtp.aash.modules.share.api;

import com.mgmtp.a12.dataservices.wcf.WorkspaceConverter;
import com.mgmtp.a12.dataservices.wcf.WorkspaceFactory;
import com.mgmtp.a12.dataservices.wcf.annotations.WcfConverter;
import com.mgmtp.a12.dataservices.wcf.domain.ModelTuple;
import com.mgmtp.a12.dataservices.wcf.domain.Workspace;
import com.mgmtp.a12.model.header.Header;
import com.mgmtp.a12.model.header.HeaderParseException;
import com.mgmtp.a12.model.header.HeaderParser;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.stereotype.Component;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * Loads A12 model JSON files (from classpath or a ZIP archive) and runs the full
 * {@link WorkspaceConverter} chain on them to produce runtime-expanded model JSON.
 *
 * <p><strong>Workspace key convention:</strong> {@code Workspace.getModels()} is a
 * {@code Map<String, ModelTuple>} keyed by the <em>full model ID</em> (e.g.
 * {@code Jugendarbeitsschutz_Jugendarbeitsschutz_DM}), <strong>not</strong> by the model
 * type string (e.g. {@code "DM"}). This matches the behaviour of A12's own
 * {@code DefaultWorkspaceSupplier}, which uses {@code header.getId()} as the key.
 *
 * <p><strong>Single shared workspace:</strong> All models from an import batch are placed
 * into <em>one</em> workspace before the converter chain runs. This is required because
 * converters such as {@code MetadataConverter} expand DM includes and joins by looking up
 * referenced models in the workspace map. Processing each model in its own isolated
 * workspace causes {@code IllegalArgumentException: Unexpected model ID} for any
 * cross-DM reference that appears in the same import batch but has not yet been stored
 * to the database.
 */
@Component
@Slf4j
public class WcfModelExpander {

    private final List<WorkspaceConverter> converters;
    private final HeaderParser headerParser;

    private final ObjectMapper mapper = new ObjectMapper();

    public WcfModelExpander(List<WorkspaceConverter> converters, HeaderParser headerParser) {
        // Spring only respects @Order/Ordered for collection injection, not A12's own
        // @WcfConverter.order(). Therefore, explicitly sort here according to the order intended by A12.
        this.converters = converters.stream()
                .sorted(Comparator.comparingInt(WcfModelExpander::converterOrder))
                .toList();
        this.headerParser = headerParser;
    }

    public List<String> convertToExpanded(@NonNull String classpathOrZip) throws Exception {
        List<String> jsonContents = classpathOrZip.startsWith("file:")
                ? loadJsonFromZip(classpathOrZip)
                : loadJsonFromClasspath(classpathOrZip);

        WorkspaceFactory f = WorkspaceFactory.getInstance();
        List<String> resultJson = new ArrayList<>();


        Workspace ws = f.createWorkspace();
        for (String jsonString : jsonContents) {
            JsonNode jsonNode = mapper.readTree(jsonString);
            Header header = readHeader(jsonNode, jsonString);
            if (header != null) {
                ws.getModels().put(header.getId(), f.createModelTuple(header, jsonString));
            }
        }

        for (WorkspaceConverter c : converters) {
            ws = c.convert(ws);
        }

        for (Map.Entry<String, ModelTuple> model : ws.getModels().entrySet()) {
            resultJson.add(model.getValue().getContent());
            log.info("Finished runtime conversion for {}", model.getKey());
        }

        return resultJson;
    }

    private List<String> loadJsonFromClasspath(String classpath) throws IOException {
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        String path = classpath.endsWith("/") ? classpath : classpath + "/";
        Resource[] resources = resolver.getResources(path + "*.json");
        List<String> result = new ArrayList<>();
        for (Resource resource : resources) {
            result.add(new String(resource.getContentAsByteArray(), StandardCharsets.UTF_8));
        }
        return result;
    }

    private List<String> loadJsonFromZip(String fileUrl) throws IOException {
        Path zipPath = Path.of(URI.create(fileUrl));
        List<String> result = new ArrayList<>();
        try (ZipFile zipFile = new ZipFile(zipPath.toFile())) {
            Enumeration<? extends ZipEntry> entries = zipFile.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                if (!entry.isDirectory() && entry.getName().endsWith(".json")) {
                    try (InputStream is = zipFile.getInputStream(entry)) {
                        result.add(new String(is.readAllBytes(), StandardCharsets.UTF_8));
                    }
                }
            }
        }
        return result;
    }

    private static int converterOrder(WorkspaceConverter converter) {
        WcfConverter annotation = AnnotationUtils.findAnnotation(converter.getClass(), WcfConverter.class);
        return annotation != null ? annotation.order() : Integer.MAX_VALUE;
    }

    private Header readHeader(JsonNode jsonNode, String jsonString) throws JacksonException,
            HeaderParseException {
        JsonNode headerNode = jsonNode.get("header");
        if (headerNode != null) {
            return headerParser.parseJson(jsonString);
        }
        return null;
    }
}
