/*-
 * ~~~~~~licensing~~~~~~
 * resource-based-storage
 * ==========
 * Copyright (C) 2020 - 2025 EmDev LLC
 * ==========
 * You may not use this file except in accordance with the License Terms of the Copyright
 * Holder located at: https://entaxy.ru/eula . All copyrights, all intellectual property
 * rights to the Software and any copies are the property of the Copyright Holder. Unless
 * it is explicitly allowed the Copyright Holder, the User is prohibited from using the
 * Software for commercial purposes to provide services to third parties.
 * 
 * The Copyright Holder hereby declares that the Software is provided on an "AS IS".
 * Under no circumstances does the Copyright Holder guarantee or promise that the
 * Software provided by him will be suitable or not suitable for the specific purposes
 * of the User, that the Software will meet all commercial and personal subjective
 * expectations of the User, that the Software will work properly, without technical
 * errors, quickly and uninterruptedly.
 * 
 * Under no circumstances shall the Copyright Holder or its Affiliates is not liable
 * to the User for any direct or indirect losses of the User, his expenses or actual
 * damage, including, downtime; loss of bussines; lost profit; lost earnings; loss
 * or damage to data, property, etc.
 * ~~~~~~/licensing~~~~~~
 */
package ru.entaxy.platform.integration.applications.storage.resource.impl.application;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.JsonElement;

import ru.entaxy.esb.resources.EntaxyResource;
import ru.entaxy.platform.integration.applications.ApplicationContent;
import ru.entaxy.platform.integration.applications.ApplicationItem;
import ru.entaxy.platform.integration.applications.ApplicationResource;
import ru.entaxy.platform.integration.applications.EntaxyApplication;
import ru.entaxy.platform.integration.applications.EntaxyApplicationRevision;
import ru.entaxy.platform.integration.applications.EntaxyApplicationVersion;
import ru.entaxy.platform.integration.applications.descriptor.ApplicationDescriptor;
import ru.entaxy.platform.integration.applications.exceptions.ApplicationVersionCausedException;
import ru.entaxy.platform.integration.applications.exceptions.ApplicationVersionException;
import ru.entaxy.platform.integration.applications.impl.StorageApplicationContentAdapter;
import ru.entaxy.platform.integration.applications.impl.StorageItemAdapter;
import ru.entaxy.platform.integration.applications.storage.resource.impl.AbstractResourceComponentWithChildren;
import ru.entaxy.platform.integration.applications.storage.resource.impl.ResourceContext;

public class ApplicationVersionComponent
        extends AbstractResourceComponentWithChildren<ApplicationVersionData, RevisionComponent>
        implements EntaxyApplicationVersion {

    private static final Logger log = LoggerFactory.getLogger(ApplicationVersionComponent.class);

    protected String versionNumber;

    protected ApplicationComponent applicationComponent;

    protected ResourceApplicationContent applicationContent;

    /*
    public ApplicationVersionComponent(EntaxyResourceService resourceService, String versionNumber,
            String parentRootUrl, ApplicationComponent applicationComponent) {
        super(resourceService);
        setEntaxyResourceService(resourceService);
        this.versionNumber = versionNumber;
        this.rootResourceFolderUrl = parentRootUrl.concat("/").concat(versionNumber);
        this.applicationComponent = applicationComponent;
        this.applicationContent = new ResourceApplicationContent(new ApplicationDescriptor());
    }
    */

    public ApplicationVersionComponent(ResourceContext parentResourceContext, String versionNumber,
            ApplicationComponent applicationComponent) {
        super(parentResourceContext.subcontext(versionNumber));
        this.versionNumber = versionNumber;
        this.applicationComponent = applicationComponent;
        this.applicationContent = ResourceApplicationContent.fromContext(this.resourceContext.subcontext(".content"));
    }

    @Override
    protected Class<ApplicationVersionData> getDataObjectClass() {
        return ApplicationVersionData.class;
    }

    @Override
    protected String getRootResourceName() {
        return ".version";
    }

    @Override
    protected ApplicationVersionData createDataObject() {
        return new ApplicationVersionData(versionNumber);
    }

    @Override
    public String getComponentKey() {
        return versionNumber;
    }

    @Override
    protected RevisionComponent createChildComponent(String key) throws Exception {
        // return new RevisionComponent(entaxyResourceService, this, key);
        return new RevisionComponent(resourceContext, this, key);
    }

    @Override
    protected List<String> getChildrenKeyList() {
        if (dataObject.revisions == null)
            return Collections.emptyList();
        return dataObject.getRevisionKeys();
    }

    @Override
    protected void addChildKey(String key) {
        dataObject.addRevision(key, Calendar.getInstance().getTimeInMillis() + "");
    }

    @Override
    protected void removeChildKey(String key) {
        dataObject.removeRevision(key);
    }

    @Override
    protected void handleChildLoadException(String key, Exception e) {
        log.error("Error loading revision [" + key + "]", e);
    }

    @Override
    public void save() {
        if (applicationContent != null && applicationContent.getDescriptor() != null) {
            JsonElement je = GSON.toJsonTree(applicationContent.getDescriptor());
            EntaxyResource descriptorResource =
                    applicationContent.getResourceContext().getResource(EntaxyApplication.DESCRIPTOR_NAME);
            try (InputStream is = IOUtils.toInputStream(je.toString())) {
                descriptorResource.save(is);
            } catch (IOException e) {
                log.error("Error saving descriptor to [" + descriptorResource.getURL() + "]", e);
            }
        }
        super.save();
    }

    public void setDescriptor(ApplicationDescriptor applicationDescriptor) {
        this.applicationContent.setDescriptor(applicationDescriptor);
    }

    /* EntaxyApplicationVersion implementation */

    @Override
    public String getVersionNumber() {
        return versionNumber;
    }

    @Override
    public List<EntaxyApplicationRevision> getRevisions() {
        return childrenCache.values().stream().collect(Collectors.toList());
    }

    @Override
    public EntaxyApplicationRevision getLastRevision() {
        try {
            return getRevision(dataObject.getLastRevision());
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public EntaxyApplicationRevision getRevision(int revision) {
        return childrenCache.get(revision + "");
    }

    @Override
    public EntaxyApplicationRevision createRevision(int fromRevision)
            throws ApplicationVersionException {
        RevisionComponent revisionComponent;
        try {
            int last = dataObject.getLastRevision();
            int revisionNumber = -1;
            ApplicationContent source = null;
            if (last == -1) {
                revisionNumber = 0;
                source = this;

            } else {
                revisionNumber = last + 1;
                int sourceRevision = fromRevision;
                if (sourceRevision < 0)
                    sourceRevision = last;
                RevisionComponent sourceComponent = childrenCache.get(sourceRevision + "");
                source = sourceComponent;
            }
            if (revisionNumber < 0)
                throw new InstantiationException("Failed defining new revision number");

            ApplicationDescriptor sourceDescriptor = source.getDescriptor();

            revisionComponent = createChildComponent(revisionNumber + "");
            revisionComponent.init();
            revisionComponent.setDescriptor(sourceDescriptor);
            revisionComponent.save();
            StorageApplicationContentAdapter adapted = revisionComponent.adapt();

            ApplicationDescriptor revisionDescriptor = revisionComponent.getDescriptor();

            // we copy only items, not resources
            Map<String, ApplicationItem> itemMap =
                    source.getItems().stream().collect(Collectors.toMap(item -> item.getLocation(), item -> item));
            List<ru.entaxy.platform.integration.applications.descriptor.ApplicationComponent> components =
                    new ArrayList<>();
            components.add(revisionDescriptor.getFeatures());
            components.addAll(revisionDescriptor.getComponents());

            for (ru.entaxy.platform.integration.applications.descriptor.ApplicationComponent component : components) {
                if (!itemMap.containsKey(component.getInternalLocation()))
                    continue;
                StorageItemAdapter itemAdapter = adapted.createItem(component);
                try (InputStream is = itemMap.get(component.getInternalLocation()).getInputStream()) {
                    itemAdapter.write(is);
                }
            }

            revisionComponent.save();

            // finalize

            synchronized (childrenCacheLock) {
                dataObject.addRevision(revisionNumber, Calendar.getInstance().getTimeInMillis() + "");
                updateRootResource();
                childrenCache.put(revisionComponent);
            }

        } catch (ApplicationVersionException rethrow) {
            throw rethrow;
        } catch (Exception e) {
            throw new ApplicationVersionCausedException(applicationComponent.getName(), e, versionNumber);
        }

        return revisionComponent;
    }

    public StorageApplicationContentAdapter adapt() {
        return applicationContent;
    }

    protected ApplicationDescriptor createCopy(ApplicationDescriptor origin) throws Exception {
        return GSON.fromJson(GSON.toJsonTree(origin, ApplicationDescriptor.class).deepCopy(),
                ApplicationDescriptor.class);
    }

    @Override
    public EntaxyApplication getApplication() {
        return applicationComponent;
    }

    @Override
    public ApplicationDescriptor getDescriptor() {
        return applicationContent.getDescriptor();
    }

    @Override
    public List<ApplicationItem> getItems() {
        return applicationContent.getItems();
    }

    @Override
    public List<ApplicationResource> getResources() {
        return applicationContent.getResources();
    }


}
