/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.unomi.plugins.baseplugin.actions;

import org.apache.commons.lang3.StringUtils;
import org.apache.unomi.api.Event;
import org.apache.unomi.api.Persona;
import org.apache.unomi.api.Profile;
import org.apache.unomi.api.PropertyType;
import org.apache.unomi.api.actions.Action;
import org.apache.unomi.api.actions.ActionExecutor;
import org.apache.unomi.api.services.EventService;
import org.apache.unomi.api.services.ProfileService;
import org.apache.unomi.persistence.spi.PropertyHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

public class UpdatePropertiesAction implements ActionExecutor {

    public static final String PROPS_TO_ADD = "add";
    public static final String PROPS_TO_UPDATE = "update";
    public static final String PROPS_TO_DELETE = "delete";
    public static final String PROPS_TO_ADD_TO_SET  = "addToSet";

    public static final String TARGET_ID_KEY = "targetId";
    public static final String TARGET_TYPE_KEY = "targetType";

    public static final String TARGET_TYPE_PROFILE = "profile";

    Logger logger = LoggerFactory.getLogger(UpdatePropertiesAction.class.getName());

    private ProfileService profileService;
    private EventService eventService;

    public int execute(Action action, Event event) {

        Profile target = event.getProfile();

        String targetId = (String) event.getProperty(TARGET_ID_KEY);
        String targetType = (String) event.getProperty(TARGET_TYPE_KEY);

        if (StringUtils.isNotBlank(targetId) && event.getProfile() != null && !targetId.equals(event.getProfile().getItemId())) {
            target = TARGET_TYPE_PROFILE.equals(targetType) ? profileService.load(targetId) : profileService.loadPersona(targetId);
            if (target == null) {
                logger.warn("No profile found with Id : {}. Update skipped.", targetId);
                return EventService.NO_CHANGE;
            }
        }

        boolean isProfileOrPersonaUpdated = false;

        Map<String, Object> propsToAdd = (HashMap<String, Object>) event.getProperties().get(PROPS_TO_ADD);

        if (propsToAdd != null) {
            isProfileOrPersonaUpdated |= processProperties(target, propsToAdd, "setIfMissing");
        }

        Map<String, Object> propsToUpdate = (HashMap<String, Object>) event.getProperties().get(PROPS_TO_UPDATE);
        if (propsToUpdate != null) {
            isProfileOrPersonaUpdated |= processProperties(target, propsToUpdate, "alwaysSet");
        }

        Map<String, Object> propsToAddToSet = (HashMap<String, Object>) event.getProperties().get(PROPS_TO_ADD_TO_SET);
        if (propsToAddToSet != null) {
            isProfileOrPersonaUpdated |= processProperties(target, propsToAddToSet, "addValues");
        }

        List<String> propsToDelete = (List<String>) event.getProperties().get(PROPS_TO_DELETE);
        if (propsToDelete != null) {
            for (String prop : propsToDelete) {
                isProfileOrPersonaUpdated |= PropertyHelper.setProperty(target, prop, null, "remove");
            }
        }

        if (StringUtils.isNotBlank(targetId) && isProfileOrPersonaUpdated &&
                event.getProfile() != null && !targetId.equals(event.getProfile().getItemId())) {
            if (TARGET_TYPE_PROFILE.equals(targetType)) {
                profileService.save(target);
                Event profileUpdated = new Event("profileUpdated", null, target, null, null, target, new Date());
                profileUpdated.setPersistent(false);
                int changes = eventService.send(profileUpdated);
                if ((changes & EventService.PROFILE_UPDATED) == EventService.PROFILE_UPDATED) {
                    profileService.save(target);
                }
            } else {
                profileService.savePersona((Persona) target);
            }

            return EventService.NO_CHANGE;

        }

        return isProfileOrPersonaUpdated ? EventService.PROFILE_UPDATED : EventService.NO_CHANGE;

    }

    private boolean processProperties(Profile target, Map<String, Object> propsMap, String strategy) {
        boolean isProfileOrPersonaUpdated = false;
        for (String prop : propsMap.keySet()) {
            PropertyType propType = null;
            if (prop.startsWith("properties.") || prop.startsWith("systemProperties.")) {
                propType = profileService.getPropertyType(prop.substring(prop.indexOf('.') + 1));
            } else {
                propType = profileService.getPropertyType(prop);
                //ideally each property must have a matching propertyType
                if(prop.equals("segments")) {
                    propsMap.put(prop, new HashSet<String>((ArrayList<String>)propsMap.get(prop)));
                }
            }
            if (propType != null) {
                isProfileOrPersonaUpdated |= PropertyHelper.setProperty(target, prop, PropertyHelper.getValueByTypeId(propsMap.get(prop), propType.getValueTypeId()), strategy);
            } else {
                isProfileOrPersonaUpdated |= PropertyHelper.setProperty(target, prop, propsMap.get(prop), strategy);
            }
        }
        return isProfileOrPersonaUpdated;
    }

    public void setProfileService(ProfileService profileService) {
        this.profileService = profileService;
    }

    public void setEventService(EventService eventService) {
        this.eventService = eventService;
    }

}
