/*-
 * ~~~~~~licensing~~~~~~
 * object-producing-config-support
 * ==========
 * 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.core.producer.config;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

import org.osgi.service.component.annotations.CollectionType;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.annotations.ReferencePolicyOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.entaxy.esb.platform.runtime.base.connecting.generator.Generated;
import ru.entaxy.platform.base.objects.EntaxyObject;
import ru.entaxy.platform.base.objects.factory.EntaxyFactory;
import ru.entaxy.platform.base.objects.factory.EntaxyFactoryException;
import ru.entaxy.platform.base.support.CommonUtils;
import ru.entaxy.platform.config.runtime.ObjectConfig;
import ru.entaxy.platform.core.producer.api.EntaxyProducer;
import ru.entaxy.platform.core.producer.api.EntaxyProducerService;
import ru.entaxy.platform.core.producer.api.EntaxyWrappedFactory;
import ru.entaxy.platform.core.producer.executor.generationmodel.GeneratedHeaders;
import ru.entaxy.platform.core.producer.impl.CommonObjectProducer;
import ru.entaxy.platform.core.producer.wrapper.AbstractFactoryWrapper;

// @Component(service = EntaxyProducer.class, immediate = true)
// @EntaxyProducerInfo (supportedTypes = {ObjectConfig.CONFIG_OBJECT_TYPE})
public class ConfigProducer2 extends CommonObjectProducer implements EntaxyProducer {

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

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    EntaxyProducerService entaxyProducerService;

    @Reference(bind = "addFactory", unbind = "removeFactory", cardinality = ReferenceCardinality.MULTIPLE,
            collectionType = CollectionType.SERVICE, policy = ReferencePolicy.DYNAMIC,
            policyOption = ReferencePolicyOption.GREEDY)
    public void addFactory(EntaxyFactory factory) {
        AbstractFactoryWrapper wrapper = super.doAddFactory(factory);
        if (wrapper != null)
            wrapper.setGenerationProcessor(this);
    }

    // WE MUST DECLARE IT for @Reference annotation processor
    @Override
    public void removeFactory(EntaxyFactory factory) {
        super.removeFactory(factory);
    }

    @Override
    public void preGenerate(EntaxyWrappedFactory wrappedFactory, String outputType, String scope, String objectType,
            Map<String, Object> parameters) throws EntaxyFactoryException {
        super.preGenerate(wrappedFactory, outputType, scope, objectType, parameters);
        @SuppressWarnings("unchecked")
        Map<String, Object> properties = (Map<String, Object>) parameters.get(EntaxyObject.FIELDS.PROPERTIES);

        // System.out.println("\n\n\tCONFIG PRODUCER :: preGenerate");
        // System.out.println("\t" + parameters.toString());

        // properties can be null for 'config-field' output
        if (properties != null) {

            /* System.out.println("\n\n\tGENERATE CONFIG: "
            		+ "\noutput: " + outputType
            		+ "\nscope: " + scope
            		+ "\nproperties: " + properties.toString()); */

            ObjectConfig.backupProperties(parameters, ObjectConfig.CONFIG_PROPERTIES_FIELD_NAME);
            ObjectConfig.removeConfigData(parameters, ObjectConfig.CONFIG_PROPERTIES_FIELD_NAME);

            Map<String, Object> configurables = new HashMap<>();
            Map<String, Object> immutables = new HashMap<>();

            String targetFactoryId = properties.get(ObjectConfig.CONFIG_FACTORY_FIELD_NAME).toString();
            String targetOutput = properties.get(ObjectConfig.CONFIG_OUTPUT_FIELD_NAME).toString();

            EntaxyFactory f = entaxyProducerService.findFactoryById(targetFactoryId);
            if (!CommonUtils.isValid(targetOutput))
                targetOutput = f.getDefaultOutput().getType();

            fillConfigMaps(f, targetOutput, scope, objectType, parameters, configurables, immutables);

            parameters.put(ObjectConfig.CONFIGURABLES_FIELD_NAME, configurables);
            parameters.put(ObjectConfig.IMMUTABLES_FIELD_NAME, immutables);

            String configuredId = parameters.get(ObjectConfig.CONFIG_ID_FIELD_NAME).toString();
            String configuredType = parameters.get(ObjectConfig.CONFIG_TYPE_FIELD_NAME).toString();

            String configurationPid = ObjectConfig.getConfigurationPid(configuredId, configuredType);
            String placeholderPrefix = ObjectConfig.getConfigurationPrefix(configuredId, configuredType);

            // set objectId to configurationPid

            parameters.put(EntaxyObject.FIELDS.OBJECT_ID, configurationPid);

            parameters.put(ObjectConfig.CONFIG_PID_FIELD_NAME, configurationPid);
            parameters.put(ObjectConfig.CONFIG_PLACEHOLDER_PREFIX_FIELD_NAME, placeholderPrefix);

        }
    }

    @Override
    public void postGenerate(EntaxyWrappedFactory wrappedFactory, String outputType, String scope, String objectType,
            Map<String, Object> parameters, Generated generated) throws EntaxyFactoryException {
        super.postGenerate(wrappedFactory, outputType, scope, objectType, parameters, generated);
        ObjectConfig.restoreBackupProperties(parameters, ObjectConfig.CONFIG_PROPERTIES_FIELD_NAME);

        @SuppressWarnings("unchecked")
        Map<String, Object> properties = (Map<String, Object>) parameters.get(EntaxyObject.FIELDS.PROPERTIES);

        // properties can be null for 'config-field' output
        if (properties != null) {

            // enrich paramaters with configured fields placeholders
            String placeholderPrefix = parameters.get(ObjectConfig.CONFIG_PLACEHOLDER_PREFIX_FIELD_NAME).toString();

            @SuppressWarnings("unchecked")
            Map<String, Object> configuredFields =
                    (Map<String, Object>) parameters.getOrDefault(ObjectConfig.CONFIGURABLES_FIELD_NAME,
                            Collections.emptyMap());
            configuredFields.putAll(
                    (Map<String, Object>) parameters.getOrDefault(ObjectConfig.IMMUTABLES_FIELD_NAME,
                            Collections.emptyMap()));

            log.debug("CONFIGURED: \n" + configuredFields);

            Map<String, Object> placeholdersMap = configuredFields.entrySet().stream()
                    // .map(entry -> entry.setValue(placeholderPrefix + entry.getKey() + "}"))
                    .collect(Collectors.toMap(Map.Entry::getKey, entry -> placeholderPrefix + entry.getKey() + "}"));

            log.debug("\n\n\tPLACEHOLDERS: \n" + placeholdersMap);

            parameters.putAll(placeholdersMap);

            // add headers
            String configuredId = parameters.get(ObjectConfig.CONFIG_ID_FIELD_NAME).toString();
            String configuredType = parameters.get(ObjectConfig.CONFIG_TYPE_FIELD_NAME).toString();

            String configurationPid = ObjectConfig.getConfigurationPid(configuredId, configuredType);

            GeneratedHeaders headers = GeneratedHeaders.getHeaders(generated.getProperties());
            headers.set(ObjectConfig.HEADER_OBJECT_CONFIG_PID,
                    configuredId + ":" + configuredType + ":" + configurationPid);
        }
    }

    protected void fillConfigMaps(EntaxyFactory factory, String outputType, String scope, String objectType,
            Map<String, Object> parameters, Map<String, Object> configurables, Map<String, Object> immutables) {

        @SuppressWarnings("unchecked")
        Map<String, Object> properties =
                (Map<String, Object>) parameters.get(ObjectConfig.CONFIG_PROPERTIES_FIELD_NAME);
        for (Entry<String, Object> entry : properties.entrySet()) {
            Map<String, Object> targetMap =
                    ObjectConfig.getTargetConfigMap(factory, outputType, entry.getKey(), configurables, immutables);
            if (targetMap == null) {
                continue;
            }
            targetMap.put(entry.getKey(), entry.getValue());

        }

    }

}
