/*******************************************************************************
 * Copyright 2Fi Business Solutions Ltd.
 *
 * This code is copyrighted. Under no circumstances should any party, people,
 * or organization should redistribute any portions of this code in any form,
 * either verbatim or through electronic media, to any third parties, unless
 * under explicit written permission by 2Fi Business Solutions Ltd.
 ******************************************************************************/
package com.ffii.tbms.api.service;

import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;

import com.ffii.core.dao.JdbcDao;
import com.ffii.core.utils.MapUtils;
import com.ffii.core.utils.Params;
import com.ffii.core.web.AbstractService;

import org.apache.commons.lang3.RandomStringUtils;
import org.jfree.util.Log;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

/**
 * Authentication API Service
 *
 * @author Patrick
 */
@Service
public class AuthenticationService extends AbstractService {

	private static SecureRandom SECURE_RANDOM = new SecureRandom();

	@Autowired
	private JdbcDao jdbcDao;

	/**
	 * Generate random token
	 * 
	 * @return 32 characters random String with letters and numbers
	 */
	private String generateToken() {
		return RandomStringUtils.random(32, 0, 0, true, true, null, SECURE_RANDOM);
	}

	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
	public String createAccessToken(int userId, String deviceId) {

		String sql = "INSERT INTO";
		sql += " access_token";
		sql += " (userId, deviceId, token, created)";
		sql += " VALUES";
		sql += " (:userId, :deviceId, :token, NOW())";
		sql += " ON DUPLICATE KEY";
		sql += " UPDATE";
		sql += " created = NOW(),";
		sql += " token = :token";

		String token = generateToken();

		Map<String, Object> args = MapUtils.toHashMap("userId", userId, "deviceId", deviceId, "token", token);
		jdbcDao.executeUpdate(sql, args);

		return token;
	}

	@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
	public Map<String, Object> findUserByAccessToken(String deviceId, String token) {

		String sql = "SELECT";
		sql += " u.id,";
		sql += " u.sysGroupId,";
		sql += " u.expired,";
		sql += " u.locked,";
		sql += " u.companyId,";
		sql += " u.username,";
		sql += " u.firstname,";
		sql += " u.lastname,";
		sql += " u.fullname";
		sql += " FROM access_token at";
		sql += " LEFT JOIN users u ON at.userId = u.id";
		sql += " WHERE u.deleted = 0";
		sql += " AND u.locked = 0";
		sql += " AND at.deviceId = :deviceId";
		sql += " AND at.token = :token";

		Map<String, Object> args = MapUtils.toHashMap("deviceId", deviceId, "token", token);
		Map<String, Object> data = jdbcDao.queryForMap(sql, args);
		if (data == null){
			logger.info("userData : null, deviceId: "+deviceId+", token: "+token);
		}
		return data;
	}

	@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
	public Map<String, Object> findUserAuth(Map<String, Object> args) {

		String sql = " SELECT * "
				+ " FROM users_authorities ua "
				+ " WHERE ua.userId = :userId "
				+ " AND ua.authority = :auth ; ";

		return jdbcDao.queryForMap(sql, args);
	}

	public boolean haveAuth(Object userId, String... auths){
        boolean haveAuth = false;

        Map<String, Object> authMap = findUserAuth(MapUtils.toHashMap("userId", userId, "auth", "SUPERUSER"));
        haveAuth = authMap != null && authMap.get("userId") == userId;
        if(haveAuth) return haveAuth;

        for(String auth:auths){
            authMap = findUserAuth(MapUtils.toHashMap("userId", userId, "auth", auth));
            haveAuth = authMap != null && authMap.get("userId") == userId;
            if(haveAuth) return haveAuth;
        }
        return haveAuth;
    }

	public Map<String, Object> getNoUserFoundMap(){
		logger.info("NoUserFound");
		Map<String, Object> map = new HashMap<>();
		map.put(Params.AUTH, Boolean.FALSE);
		map.put(Params.SUCCESS, Boolean.FALSE);
		map.put(Params.MSG, "pleaseReLogin");
		return map;
	}

	public Map<String, Object> getNoAuthFoundMap(){
		logger.info("NoAuth");
		Map<String, Object> map = new HashMap<>();
		map.put(Params.AUTH, Boolean.FALSE);
		map.put(Params.SUCCESS, Boolean.FALSE);
		return map;
	}

}
