/*-
 * ~~~~~~licensing~~~~~~
 * object-runtime-core
 * ==========
 * 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.objects.runtime.impl;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.osgi.framework.Filter;
import org.osgi.service.component.annotations.Component;
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 ru.entaxy.platform.base.support.CommonUtils;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeObject;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeObjectContainer;
import ru.entaxy.platform.objects.runtime.EntaxyRuntimeObjectService;
import ru.entaxy.platform.search.AbstractSearchProvider;
import ru.entaxy.platform.search.SearchProvider;
import ru.entaxy.platform.search.SearchProviderInfo;
import ru.entaxy.platform.search.SearchService;

@Component(service = SearchProvider.class, immediate = true)
@SearchProviderInfo(id = "runtime",
        targetTypes = {RuntimeObjectSearchProvider.TYPE_OBJECT, RuntimeObjectSearchProvider.TYPE_CONTAINER})
public class RuntimeObjectSearchProvider extends AbstractSearchProvider {

    public static final String TYPE_OBJECT = "object";
    public static final String TYPE_CONTAINER = "container";

    @Reference(cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.DYNAMIC,
            policyOption = ReferencePolicyOption.GREEDY)
    volatile SearchService searchService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.DYNAMIC,
            policyOption = ReferencePolicyOption.GREEDY)
    volatile EntaxyRuntimeObjectService objectService;

    @Override
    public Map<String, String> getStandardAttributes(String targetType) throws IllegalArgumentException {

        if (TYPE_CONTAINER.equalsIgnoreCase(targetType))
            return EntaxyRuntimeObjectContainer.SEARCH.STANDARD_ATTRIBUTES;

        if (TYPE_OBJECT.equalsIgnoreCase(targetType)) {
            Map<String, String> result = new LinkedHashMap<>();
            result.putAll(EntaxyRuntimeObject.SEARCH.STANDARD_ATTRIBUTES);

            result.putAll(CommonUtils.prefixMap(EntaxyRuntimeObjectContainer.SEARCH.STANDARD_ATTRIBUTES, "container."));

            // factory fields we'll get via search service
            // for 'runtime-only' nodes the information can be unavailable

            try {
                result.putAll(CommonUtils.prefixMap(searchService.getStandardAttributes("factory"), "factory."));
            } catch (Exception ignore) {
                // NOOP
            }

            return result;
        }

        throw new IllegalArgumentException(
                String.format("Provider [%s]: type [%s] nor supported", getProviderId(), targetType));
    }

    @Override
    public List<Map<String, Object>> search(String targetType, Filter filter) throws IllegalArgumentException {
        if (TYPE_CONTAINER.equalsIgnoreCase(targetType))
            return searchContainers(filter);

        if (TYPE_OBJECT.equalsIgnoreCase(targetType))
            return searchObjects(filter);

        throw new IllegalArgumentException(
                String.format("Provider [%s]: type [%s] nor supported", getProviderId(), targetType));
    }

    protected List<Map<String, Object>> searchObjects(Filter filter) {
        return objectService.getObjects().stream()
                .map(obj -> getObjectEssence(obj))
                .filter(ess -> filter.matches(ess))
                .collect(Collectors.toList());
    }

    protected Map<String, Object> getObjectEssence(EntaxyRuntimeObject object) {

        Map<String, Object> result = new LinkedHashMap<>();

        // object attributes
        result.put(EntaxyRuntimeObject.SEARCH.ATTR_ID, object.getId());
        result.put(EntaxyRuntimeObject.SEARCH.ATTR_TYPE, object.getType());
        result.put(EntaxyRuntimeObject.SEARCH.ATTR_LABEL, object.getAllAttributes().getOrDefault("label", ""));
        result.put(EntaxyRuntimeObject.SEARCH.ATTR_DISPLAY_NAME, object.getDisplayName());
        result.put(EntaxyRuntimeObject.SEARCH.ATTR_FACTORY, object.getFactoryId());
        result.put(EntaxyRuntimeObject.SEARCH.ATTR_SCOPE, object.getScope());
        result.put(EntaxyRuntimeObject.SEARCH.ATTR_RESOURCES, object.getResources().stream()
                .map(res -> String.format("%s;%s", res.getResourceUrl(), res.getScope().name()))
                .collect(Collectors.toList()));
        result.put(EntaxyRuntimeObject.SEARCH.ATTR_RELATIONS_IN,
                object.getIncomingRelations().stream().map(rel -> String.format("%s;%S;%s:%s",
                        rel.getSource().getObjectFullId(), rel.getType().name(), rel.getName(), rel.getGroup()))
                        .collect(Collectors.toList()));
        result.put(EntaxyRuntimeObject.SEARCH.ATTR_RELATIONS_OUT,
                object.getRelations().stream().map(rel -> String.format("%s;%S;%s:%s",
                        rel.getTarget().getObjectFullId(), rel.getType().name(), rel.getName(), rel.getGroup()))
                        .collect(Collectors.toList()));

        // container attributes
        result.putAll(CommonUtils.prefixMap(getContainerEssence(object.getContainer()), "container."));

        // factory attributes we'll get via search service
        // for 'runtime-only' nodes the information can be unavailable

        try {
            List<Map<String, Object>> factories = searchService.search("factory",
                    String.format("(&(id=%s)(type=%s))", object.getFactoryId(), object.getType()));
            if (factories != null && !factories.isEmpty())
                result.putAll(CommonUtils.prefixMap(factories.get(0), "factory."));
        } catch (Exception ignore) {
            // NOOP
        }

        return result;
    }

    protected List<Map<String, Object>> searchContainers(Filter filter) {
        return objectService.getContainers().stream()
                .map(obj -> getContainerEssence(obj))
                .filter(ess -> filter.matches(ess))
                .collect(Collectors.toList());
    }

    protected Map<String, Object> getContainerEssence(EntaxyRuntimeObjectContainer container) {
        Map<String, Object> result = new LinkedHashMap<>();

        result.put(EntaxyRuntimeObjectContainer.SEARCH.ATTR_ID, container.getId());
        result.put(EntaxyRuntimeObjectContainer.SEARCH.ATTR_TYPE, container.getType());
        result.put(EntaxyRuntimeObjectContainer.SEARCH.ATTR_NAME, container.getName());
        result.put(EntaxyRuntimeObjectContainer.SEARCH.ATTR_STATE, container.getState());

        return result;
    }

}
