/*
 * Copyright 2015-2020 OpenCB
 *
 * Licensed 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.opencb.opencga.catalog.auth.authentication;

import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.lang3.RandomStringUtils;
import org.opencb.commons.datastore.core.QueryOptions;
import org.opencb.opencga.catalog.db.DBAdaptorFactory;
import org.opencb.opencga.catalog.db.api.UserDBAdaptor;
import org.opencb.opencga.catalog.exceptions.CatalogAuthenticationException;
import org.opencb.opencga.catalog.exceptions.CatalogDBException;
import org.opencb.opencga.catalog.exceptions.CatalogException;
import org.opencb.opencga.catalog.utils.ParamUtils;
import org.opencb.opencga.core.common.MailUtils;
import org.opencb.opencga.core.config.Email;
import org.opencb.opencga.core.models.user.AuthenticationResponse;
import org.opencb.opencga.core.models.user.User;
import org.opencb.opencga.core.response.OpenCGAResult;
import org.slf4j.LoggerFactory;

import java.security.Key;
import java.util.List;
import java.util.Map;

/**
 * @author Jacobo Coll &lt;jacobo167@gmail.com&gt;
 */
public class CatalogAuthenticationManager extends AuthenticationManager {

    public static final String INTERNAL = "internal";
    private final Email emailConfig;

    private final UserDBAdaptor userDBAdaptor;

    public CatalogAuthenticationManager(DBAdaptorFactory dbAdaptorFactory, Email emailConfig, String secretKeyString, long expiration) {
        super(expiration);

        this.emailConfig = emailConfig;
        this.userDBAdaptor = dbAdaptorFactory.getCatalogUserDBAdaptor();

        Key secretKey = this.converStringToKeyObject(secretKeyString, SignatureAlgorithm.HS256.getJcaName());
        this.jwtManager = new JwtManager(SignatureAlgorithm.HS256.getValue(), secretKey);

        this.logger = LoggerFactory.getLogger(CatalogAuthenticationManager.class);
    }

    @Override
    public AuthenticationResponse authenticate(String userId, String password) throws CatalogAuthenticationException {
        try {
            userDBAdaptor.authenticate(userId, password);
            return new AuthenticationResponse(createToken(userId));
        } catch (CatalogDBException e) {
            throw new CatalogAuthenticationException("Could not validate '" + userId + "' password\n" + e.getMessage(), e);
        }
    }

    @Override
    public AuthenticationResponse refreshToken(String refreshToken) throws CatalogAuthenticationException {
        String userId = getUserId(refreshToken);
        if (!"*".equals(userId)) {
            return new AuthenticationResponse(createToken(userId));
        } else {
            throw new CatalogAuthenticationException("Cannot refresh token for '*'");
        }
    }

    @Override
    public List<User> getUsersFromRemoteGroup(String group) throws CatalogException {
        throw new UnsupportedOperationException();
    }

    @Override
    public List<User> getRemoteUserInformation(List<String> userStringList) throws CatalogException {
        throw new UnsupportedOperationException();
    }

    @Override
    public List<String> getRemoteGroups(String token) throws CatalogException {
        throw new UnsupportedOperationException();
    }

    @Override
    public void changePassword(String userId, String oldPassword, String newPassword) throws CatalogException {
        userDBAdaptor.changePassword(userId, oldPassword, newPassword);
    }

    @Override
    public void newPassword(String userId, String newPassword) throws CatalogException {
        userDBAdaptor.changePassword(userId, "", newPassword);
    }

    @Override
    public String createToken(String userId, Map<String, Object> claims, long expiration) {
        return jwtManager.createJWTToken(userId, claims, expiration);
    }

    @Override
    public String createNonExpiringToken(String userId, Map<String, Object> claims) {
        return jwtManager.createJWTToken(userId, claims, 0L);
    }

    @Override
    public OpenCGAResult resetPassword(String userId) throws CatalogException {
        ParamUtils.checkParameter(userId, "userId");
        OpenCGAResult result = null;
        String newPassword = RandomStringUtils.randomAlphanumeric(12);

        OpenCGAResult<User> user =
                userDBAdaptor.get(userId, new QueryOptions(QueryOptions.INCLUDE, UserDBAdaptor.QueryParams.EMAIL.key()));

        if (user == null || user.getNumResults() != 1) {
            throw new CatalogException("Could not retrieve the user e-mail.");
        }

        String email = user.first().getEmail();

        String mailUser = this.emailConfig.getUser();
        String mailPassword = this.emailConfig.getPassword();
        String mailHost = this.emailConfig.getHost();
        String mailPort = this.emailConfig.getPort();
        try {
            MailUtils.sendResetPasswordMail(email, newPassword, mailUser, mailPassword, mailHost, mailPort);
            result = userDBAdaptor.resetPassword(userId, email, newPassword);
        } catch (Exception e) {
            throw new CatalogException("Email could not be sent.", e);
        }

        return result;
    }
}
