/*
 * Computoser is a music-composition algorithm and a website to present the results
 * Copyright (C) 2012-2014  Bozhidar Bozhanov
 *
 * Computoser is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * Computoser is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Computoser.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.music.service;

import java.util.List;
import java.util.UUID;

import javax.inject.Inject;

import org.hibernate.StaleStateException;
import org.joda.time.DateTime;
import org.joda.time.Period;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.social.connect.Connection;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.music.dao.UserDao;
import com.music.model.persistent.SocialAuthentication;
import com.music.model.persistent.User;
import com.music.service.auth.JpaConnectionRepository;
import com.music.util.SecurityUtils;

@Service
public class UserService {

    private static final Logger logger = LoggerFactory.getLogger(UserService.class);

    @Inject
    private UserDao userDao;

    @Value("${hmac.key}")
    private String hmacKey;

    @Transactional
    public void connect(Long userId, SocialAuthentication auth) {
        List<SocialAuthentication> existingAuths = userDao.getSocialAuthentications(auth.getProviderId(), auth.getProviderUserId());

        if (existingAuths.isEmpty()) {
            User user = userDao.getById(User.class, userId);
            auth.setUser(user);
            userDao.persist(auth);
        } else {
            SocialAuthentication existingAuth = existingAuths.get(0);
            existingAuth.setExpirationTime(auth.getExpirationTime());
            existingAuth.setRefreshToken(auth.getRefreshToken());
            existingAuth.setImageUrl(auth.getImageUrl());
            userDao.persist(existingAuth);
        }
    }

    @Transactional
    public void deleteSocialAuthentication(Long userId, String providerId) {
        userDao.deleteSocialAuthentication(userId, providerId);
    }

    @Transactional(readOnly=true)
    public User getUser(long id) {
        return userDao.getById(User.class, id);
    }

    @Transactional(readOnly=true)
    public User getUserByEmail(String email) {
        return userDao.getByPropertyValue(User.class, "email", email);
    }

    @Transactional
    public User completeUserRegistration(String email, String username, String names, Connection<?> connection, boolean loginAutomatically, boolean receiveDailyDigest) {
        User user = new User();
        user.setEmail(email);
        user.setNames(names);
        user.setUsername(username);
        user.setLoginAutomatically(loginAutomatically);
        user.setRegistrationTime(new DateTime());
        user.setReceiveDailyDigest(receiveDailyDigest);
        user = userDao.persist(user);
        if (connection != null) {
            SocialAuthentication auth = JpaConnectionRepository.connectionToAuth(connection);
            auth.setUser(user);
            userDao.persist(auth);
        }
        return user;
    }

    @Transactional
    public void unsubscribe(long id, String hash) {
        User user = userDao.getById(User.class, id);
        if (hash.equals(SecurityUtils.hmac(user.getEmail(), hmacKey))) {
            user.setReceiveDailyDigest(false);
            userDao.persist(user);
        }
    }

    /**
     * http://jaspan.com/improved_persistent_login_cookie_best_practice
     */
    @Transactional(rollbackFor=StaleStateException.class)
    public User rememberMeLogin(String token, String series) {
        User existingLogin = userDao.getLoginFromAuthToken(token, series);
        if (existingLogin == null) {
            User loginBySeries = userDao.getByPropertyValue(User.class, "loginSeries", series);
            // if a login series exists, assume the previous token was stolen, so deleting all persistent logins.
            // An exception is a request made within a few seconds from the last login time
            // which may mean request from the same browser that is not yet aware of the renewed cookie
            if (loginBySeries != null && new Period(loginBySeries.getLastLoginTime(), new DateTime()).getSeconds() < 5) {
                logger.info("Assuming login cookies theft; deleting all sessions for user " + loginBySeries);
                loginBySeries.setLoginSeries(null);
                loginBySeries.setLoginToken(null);
                userDao.persist(loginBySeries);
            } else if (logger.isDebugEnabled()) {
                logger.debug("No existing login found for token=" + token + ", series=" + series);
            }
            return null;
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Existing login found for token=" + token + " and series=" + series);
        }
        fillUserWithNewTokens(existingLogin, series);
        return existingLogin;
    }

    @Transactional
    public void fillUserWithNewTokens(User user, String series) {
        user.setLoginToken(UUID.randomUUID().toString());
        user.setLoginSeries(series != null ? series : UUID.randomUUID().toString());
        user.setLastLoginTime(new DateTime());

        userDao.persist(user);
    }

    @Transactional(readOnly=true)
    public SocialAuthentication getTwitterAuthentication(User user) {
        if (user == null) {
            return null;
        }
        SocialAuthentication auth = userDao.getTwitterAuthentication(user);
        return auth;
    }
}