/*-
 * ~~~~~~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.List;
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.core.artifact.ArtifactCoordinates;
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.descriptor.ApplicationComponent;
import ru.entaxy.platform.integration.applications.descriptor.ApplicationDescriptor;
import ru.entaxy.platform.integration.applications.exceptions.RevisionException;
import ru.entaxy.platform.integration.applications.impl.StorageApplicationContentAdapter;
import ru.entaxy.platform.integration.applications.impl.application.RevisionHelper;
import ru.entaxy.platform.integration.applications.storage.resource.impl.AbstractResourceComponent;
import ru.entaxy.platform.integration.applications.storage.resource.impl.ResourceContext;

public class RevisionComponent extends AbstractResourceComponent<RevisionData>
        implements EntaxyApplicationRevision, RevisionHelper.RevisionHelperAdapter {

    private static final Logger LOG = LoggerFactory.getLogger(RevisionComponent.class);

    int revisionNumber;

    ApplicationVersionComponent versionComponent;

    ResourceApplicationContent applicationContent;

    final String CONTENT_RESOURCE_NAME = "version.content";

    // REVISION_STATUS status = REVISION_STATUS.NEW;

    /*
    public RevisionComponent(EntaxyResourceService entaxyResourceService,
            ApplicationVersionComponent versionComponent,
            String revisionNumber) {
        super(entaxyResourceService);
        this.revisionNumber = revisionNumber;
        this.versionComponent = versionComponent;
        this.rootResourceFolderUrl =
                versionComponent.getRootResourceFolderUrl().concat("/").concat(this.revisionNumber);
        applicationContent.getProperties().put(STANDARD_PROPERTIES.APPLICATION, versionComponent.getVersionNumber());
        applicationContent.getProperties().put(STANDARD_PROPERTIES.VERSION, revisionNumber);
    
        // buildComponent = new BuildComponent(entaxyResourceService, rootResourceFolderUrl);
    }
    */

    public RevisionComponent(ResourceContext resourceContext,
            ApplicationVersionComponent versionComponent,
            int revisionNumber) {
        super(resourceContext.subcontext(revisionNumber + ""));
        this.revisionNumber = revisionNumber;
        this.versionComponent = versionComponent;
        applicationContent = ResourceApplicationContent.fromContext(this.resourceContext.subcontext(".content"));
    }

    public RevisionComponent(ResourceContext resourceContext,
            ApplicationVersionComponent versionComponent,
            String revisionNumber) {
        this(resourceContext, versionComponent, Integer.parseInt(revisionNumber));
    }

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

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

    @Override
    protected RevisionData createDataObject() {
        return new RevisionData(this.revisionNumber + "");
    }

    @Override
    public String getComponentKey() {
        return revisionNumber + "";
    }

    @Override
    public void init() {
        super.init();
        initContent();
    }


    @Override
    public void save() {
        if (applicationContent != null)
            if (applicationContent.isDirty) {
                if (revisionNumber > 0) {
                    try {
                        RevisionHelper helper = new RevisionHelper(applicationContent, this);
                        helper.update();
                    } catch (Exception e) {
                        LOG.error("ERROR saving revision", e);
                        return;
                    }
                }
                applicationContent.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();
    }

    protected void initContent() {}

    public StorageApplicationContentAdapter adapt() {
        return applicationContent;
    }

    public void setDescriptor(ApplicationDescriptor applicationDescriptor) {
        try {
            this.applicationContent.setDescriptor(createCopy(applicationDescriptor));
        } catch (Exception e) {
            LOG.error("Error setting descriptor", e);
            return;
        }
    }

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

    protected void changeDescriptorComponentsVersion() {
        List<ApplicationComponent> components = new ArrayList<>();
        components.add(applicationContent.getDescriptor().features);

        components.addAll(applicationContent.getDescriptor().getComponents());

        List<String> revisionableIds = applicationContent.getItems().stream().filter(item -> item.isRevisionable())
                .map(item -> item.getId()).collect(Collectors.toList());

        for (ApplicationComponent component : components) {

            if (!revisionableIds.contains(component.id))
                continue;

            ArtifactCoordinates coordinates = ArtifactCoordinates.fromUrl(component.mavenLocation);
            coordinates.qualifier(String.format("REV_%03d", revisionNumber));
            component.mavenLocation = coordinates.toMavenString();
            coordinates = ArtifactCoordinates.fromUrl(component.targetLocation);
            coordinates.qualifier(String.format("REV_%03d", revisionNumber));
            component.targetLocation = coordinates.toMavenString();
        }

    }

    /* EntaxyApplicationRevision implementation */


    @Override
    public int getRevisionNumber() {
        return revisionNumber;
    }

    @Override
    public REVISION_STATUS getStatus() {
        return dataObject.getStatus();
    }

    @Override
    public void configure() throws RevisionException {
        RevisionHelper helper = new RevisionHelper(applicationContent, this);
        helper.configure();
    }

    @Override
    public void deploy() throws RevisionException {
        RevisionHelper helper = new RevisionHelper(applicationContent, this);
        helper.deploy();
    }

    @Override
    public void install() throws RevisionException {
        RevisionHelper helper = new RevisionHelper(applicationContent, this);
        helper.install();
    }

    @Override
    public void installDev() throws RevisionException {
        RevisionHelper helper = new RevisionHelper(applicationContent, this);
        helper.installDev();
    }

    @Override
    public void uninstall() throws RevisionException {
        RevisionHelper helper = new RevisionHelper(applicationContent, this);
        helper.uninstall();
    }

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

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

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

    // RevisionHelper.RevisionHelperAdapter implementation

    @Override
    public REVISION_STATUS getCurrentStatus() {
        return getStatus();
    }

    @Override
    public void updateStatus(REVISION_STATUS newStatus) {
        this.dataObject.status = newStatus;
        try {
            updateRootResource();
        } catch (Exception e) {
            LOG.error("Error updating root resource", e);
        }
    }

    @Override
    public void writeItem(String location, InputStream inputStream) throws Exception {
        applicationContent.getItemAdapter(location).write(inputStream);
    }

}
