package zall.manager;

import zall.bean.Folder;
import zall.bean.Media;
import zall.bean.User;
import zall.util.ServletUtil;
import zutil.Hasher;
import zutil.db.DBConnection;
import zutil.log.LogUtil;

import javax.servlet.http.*;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.logging.Logger;

import static zall.ZalleryConstants.*;


/**
 *
 */
public class AuthenticationManager {
    private static final Logger logger = LogUtil.getLogger();

    public static String generatePasswordHash(String password, String salt) {
        return Hasher.PBKDF2(password, salt, 1000);
    }

    /**
     * Authenticate a username and password and return the associated Uaer object
     */
    public static User authenticate(DBConnection db, String email, String password, HttpServletRequest request, HttpServletResponse response) throws SQLException {
        User user = User.load(db, email);

        // Valid email?
        if (user != null) {
            if (user.getPasswordHash().equals(generatePasswordHash(password, user.getPasswordSalt()))) {
                setUserAuthenticated(db, user, User.AuthType.USER_INPUT, request, response);
                return user;
            } else {
                logger.fine("Incorrect password for username: " + email);
            }
        } else {
            logger.fine("Incorrect username provided: " + email);
        }
        return null;
    }

    /**
     * Uses a cookie to authenticate a user,
     *
     * @return a user object or null authentications fails
     */
    public static User authenticate(DBConnection db, HttpServletRequest request, HttpServletResponse response) throws SQLException {
        String cookieHash = ServletUtil.getCookieValue(request.getCookies(), COOKIE_KEY_USER_HASH);
        if (cookieHash != null) {
            User user = User.loadByCookieHash(db, cookieHash);

            if (user != null) {
                if (request.getRemoteAddr().equals(user.getIpHost()) &&
                        user.getLoginDate().getTime() + SESSION_TIMEOUT > System.currentTimeMillis()) {
                    setUserAuthenticated(db, user, User.AuthType.COOKIE, request, response);
                    return user;
                } else {
                    logger.fine("Cookie hash has expired or have incorrect ipHost: " + cookieHash);
                }
            } else {
                logger.fine("Cookie hash not associated with any user: " + cookieHash);
            }
        }
        return null;
    }

    private static void setUserAuthenticated(DBConnection db, User user, User.AuthType authType, HttpServletRequest request, HttpServletResponse response) throws SQLException {
        user.setLoginDate(new Timestamp(System.currentTimeMillis()));
        user.setAuthBy(authType);
        user.setIpHost(request.getRemoteAddr());
        user.setCookieHash(Hasher.SHA1(Math.random()));
        user.save(db);

        setUserSession(user, request.getSession());
        if (authType != User.AuthType.COOKIE) {
            Cookie c = new Cookie(COOKIE_KEY_USER_HASH, user.getCookieHash());
            c.setMaxAge(COOKIE_TIMEOUT);
            response.addCookie(c);
        }

        logger.info("User(" + user.getEmail() + ") successfully authenticated by " + user.getAuthBy());
    }

    /**
     * @return the User associated with the provided session.
     */
    public static User getUserSession(HttpSession session) {
        return (User) session.getAttribute(SESSION_KEY_USER_OBJ);
    }

    public static void setUserSession(User user, HttpSession session) {
        session.setAttribute(SESSION_KEY_USER_OBJ, user);
    }

    public static void rmUserSession(HttpSession session) {
        session.removeAttribute(SESSION_KEY_USER_OBJ);
    }

    /**
     * @return true if the user has correctly been authenticated
     */
    public static boolean isValid(User user, HttpServletRequest request) {
        if (user == null) {
            logger.fine("Invalid user, user is null.");
            return false;
        }
        if (!user.isEnabled()) {
            logger.fine("Invalid user(" + user.getEmail() + "), user disabled.");
            return false;
        }

        switch (user.getAuthBy()) {
            case USER_INPUT:
                if (!user.isEmailVerified()) {
                    logger.fine("Invalid user(" + user.getEmail() + "), email not verified");
                    return false;
                }
            case COOKIE:
                if (user.getCookieHash() == null || user.getCookieHash().isEmpty()) {
                    logger.fine("Invalid user(" + user.getEmail() + "), null or empty cookie hash.");
                    return false;
                }
                String cookieHash = ServletUtil.getCookieValue(request.getCookies(), COOKIE_KEY_USER_HASH);
                if (!user.getCookieHash().equals(cookieHash)) {
                    logger.fine("Invalid user(" + user.getEmail() + "), " +
                            "cookie hash not matching, (server) " + user.getCookieHash() + " == (user) " + cookieHash + ".");
                    return false;
                }
                if (!user.getIpHost().equals(request.getRemoteAddr())) {
                    logger.fine("Invalid user(" + user.getEmail() + "), " +
                            "ip host not matching, (server) " + user.getIpHost() + " == (user) " + request.getRemoteAddr() + ".");
                    return false;
                }
                return true;
            default:
                logger.severe("Unknown user authentication type: " + user.getAuthBy());
        }
        logger.severe("Invalid user.");
        return false;
    }

    /**
     * @return true if the specified user can edit the media
     */
    public static boolean canEdit(User user, Media target) {
        return target != null && (user.isSuperUser() || target.getUser().equals(user));
    }

    /**
     * @return true if the specified user can edit the media
     */
    public static boolean canEdit(User user, Folder target) {
        return target != null && (user.isSuperUser() || user.equals(target.getUser()));
    }

    /**
     * @return true if the specified user can edit the profile of the other user
     */
    public static boolean canEdit(User user, User target) {
        return user.equals(target) || user.isSuperUser();
    }


    /**
     * Reset the user authentication. In plain word: logout user.
     */
    public static void reset(DBConnection db, User user) throws SQLException {
        user.setCookieHash(null);
        user.save(db);
    }

}
