/*
 * 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.rest.endpoints;

import org.apache.commons.lang3.StringUtils;
import org.apache.cxf.rs.security.cors.CrossOriginResourceSharing;
import org.apache.unomi.api.Event;
import org.apache.unomi.api.EventsCollectorRequest;
import org.apache.unomi.api.Persona;
import org.apache.unomi.api.Profile;
import org.apache.unomi.api.Session;
import org.apache.unomi.api.services.ConfigSharingService;
import org.apache.unomi.api.services.EventService;
import org.apache.unomi.api.services.PrivacyService;
import org.apache.unomi.api.services.ProfileService;
import org.apache.unomi.rest.models.EventCollectorResponse;
import org.apache.unomi.rest.service.RestServiceUtils;
import org.apache.unomi.utils.Changes;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jws.WebService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.Date;
import java.util.UUID;

@WebService
@Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
@Consumes(MediaType.APPLICATION_JSON)
@CrossOriginResourceSharing(allowAllOrigins = true, allowCredentials = true)
@Path("/")
@Component(service = EventsCollectorEndpoint.class, property = "osgi.jaxrs.resource=true")
public class EventsCollectorEndpoint {
    private static final Logger logger = LoggerFactory.getLogger(EventsCollectorEndpoint.class.getName());

    @Reference
    private EventService eventService;
    @Reference
    private ProfileService profileService;
    @Reference
    private PrivacyService privacyService;
    @Reference
    private ConfigSharingService configSharingService;
    @Reference
    private RestServiceUtils restServiceUtils;

    @Context
    HttpServletRequest request;
    @Context
    HttpServletResponse response;

    @OPTIONS
    @Path("/eventcollector")
    public Response options() {
        return Response.status(Response.Status.NO_CONTENT).build();
    }

    @GET
    @Path("/eventcollector")
    public EventCollectorResponse collectAsGet(@QueryParam("payload") @NotNull @Valid EventsCollectorRequest eventsCollectorRequest,
                                               @QueryParam("timestamp") Long timestampAsString) {
        return doEvent(eventsCollectorRequest, timestampAsString);
    }

    @POST
    @Path("/eventcollector")
    public EventCollectorResponse collectAsPost(@NotNull @Valid EventsCollectorRequest eventsCollectorRequest,
            @QueryParam("timestamp") Long timestampAsLong) {
        return doEvent(eventsCollectorRequest, timestampAsLong);
    }

    private EventCollectorResponse doEvent(EventsCollectorRequest eventsCollectorRequest, Long timestampAsLong) {
        Date timestamp = new Date();
        if (timestampAsLong != null) {
            timestamp = new Date(timestampAsLong);
        }

        String sessionId = eventsCollectorRequest.getSessionId();
        if (sessionId == null) {
            sessionId = request.getParameter("sessionId");
        }
        Session session = null;
        if (sessionId != null) {
            session = profileService.loadSession(sessionId, timestamp);
        }
        Profile profile = null;
        if (session == null) {
            String scope = "systemscope";
            // Get the first available scope that is not equal to systemscope to create the session otherwise systemscope will be used
            for (Event event : eventsCollectorRequest.getEvents()) {
                if (StringUtils.isNotBlank(event.getEventType())) {
                    if (StringUtils.isNotBlank(event.getScope()) && !event.getScope().equals("systemscope")) {
                        scope = event.getScope();
                        break;
                    } else if (event.getSource() != null && StringUtils.isNotBlank(event.getSource().getScope()) && !event.getSource()
                            .getScope().equals("systemscope")) {
                        scope = event.getSource().getScope();
                        break;
                    }
                }
            }
            logger.debug("scope is now {}", scope);
            String cookieProfileId = restServiceUtils.getProfileIdCookieValue(request);
            if (StringUtils.isNotBlank(cookieProfileId)) {
                profile = profileService.load(cookieProfileId);
            }
            if (profile == null) {
                // Create non persisted profile to create the session
                profile = new Profile("temp_" + UUID.randomUUID().toString());
                profile.setProperty("firstVisit", timestamp);
            }
            /*
            // Create anonymous profile so we don't keep track of the temp profile anywhere
            Profile anonymousProfile = privacyService.getAnonymousProfile(profile);
            // Create new session which should not be persisted as well as the temp profile
            session = new Session(sessionId, anonymousProfile, timestamp, scope);
            if (logger.isDebugEnabled()) {
                logger.debug("No session found for sessionId={}, creating new session!", sessionId);
            }
            */
        } else {
            Profile sessionProfile = session.getProfile();
            final String errorMessage = String
                    .format("No valid profile found or persona found for profileId=%s, aborting request !", session.getProfileId());
            if (sessionProfile.getItemId() != null) {
                // Reload up-to-date profile
                profile = profileService.load(sessionProfile.getItemId());
                if (profile == null || profile instanceof Persona) {
                    logger.error(errorMessage);
                    throw new BadRequestException(errorMessage);
                }
            } else {
                // Session uses anonymous profile, try to find profile from cookie
                String cookieProfileId = restServiceUtils.getProfileIdCookieValue(request);
                if (StringUtils.isNotBlank(cookieProfileId)) {
                    profile = profileService.load(cookieProfileId);
                }

                if (profile == null) {
                    logger.error(errorMessage);
                    throw new BadRequestException(errorMessage);
                }
            }
        }

        Changes changesObject = restServiceUtils
                .handleEvents(eventsCollectorRequest.getEvents(), session, profile, request, response, timestamp);
        int changes = changesObject.getChangeType();
        profile = changesObject.getProfile();

        if ((changes & EventService.PROFILE_UPDATED) == EventService.PROFILE_UPDATED) {
            profileService.save(profile);
        }
        if ((changes & EventService.SESSION_UPDATED) == EventService.SESSION_UPDATED && session != null) {
            profileService.saveSession(session);
        }
        if ((changes & EventService.ERROR) == EventService.ERROR) {
            String errorMessage = "Error processing events. Total number of processed events: " + changesObject.getProcessedItems() + "/"
                    + eventsCollectorRequest.getEvents().size();
            throw new BadRequestException(errorMessage);
        }

        return new EventCollectorResponse(changes);
    }
}
