/* Copyright 2014 Danish Maritime Authority.
 *
 *  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 net.maritimecloud.portal.resource;

import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import net.maritimecloud.common.resource.AbstractCommandResource;
import net.maritimecloud.identityregistry.command.api.SendResetPasswordInstructions;
import net.maritimecloud.identityregistry.domain.Identity;
import net.maritimecloud.identityregistry.domain.IdentityService;
import net.maritimecloud.identityregistry.domain.Role;
import net.maritimecloud.portal.application.ApplicationServiceRegistry;
import net.maritimecloud.portal.security.AuthenticationException;
import net.maritimecloud.portal.security.AuthenticationUtil;
import net.maritimecloud.portal.security.UserNotLoggedInException;
import org.apache.shiro.authc.UnknownAccountException;
import org.axonframework.commandhandling.gateway.CommandGateway;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Service resource for authenticating a user and getting associated roles etc...
 * <p>
 * @author Christoffer Børrild
 */
@Path("/authentication")
public class AuthenticationResource extends AbstractCommandResource {

    private static final Logger LOG = LoggerFactory.getLogger(AuthenticationResource.class);

    @Override
    protected CommandGateway commandGateway() {
        return ApplicationServiceRegistry.commandGateway();
    }

    protected AuthenticationUtil authenticationUtil() {
        return ApplicationServiceRegistry.authenticationUtil();
    }

    private IdentityService identityService() {
        return ApplicationServiceRegistry.identityService();
    }

    private LogService logService() {
        return ApplicationServiceRegistry.logService();
    }

    @POST
    @Path("/login")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public SubjectDTO login(CredentialsDTO credentials) {
        assertCredentialsNotNull(credentials);
        return tryLogin(credentials);
    }

    private SubjectDTO tryLogin(CredentialsDTO credentials) throws UserNotAuthenticated {
        try {
            return doLogin(credentials);
        } catch (AuthenticationException e) {
            reportWrongUsernamePassword(credentials);
            throw new UserNotAuthenticated();
        }
    }

    private SubjectDTO doLogin(CredentialsDTO credentials) throws AuthenticationException {
        authenticationUtil().login(credentials.username, credentials.password);
        reportUserLoggedIn(credentials.username);
        return currentSubject();
    }

    private void assertCredentialsNotNull(CredentialsDTO credentials) throws IllegalArgumentException {
        if (credentials == null) {
            throw new IllegalArgumentException();
        }

        if (credentials.getUsername() == null) {
            throw new IllegalArgumentException();
        }
    }

    @POST
    @Path("/logout")
    public void logout() {
        logService().reportUserLoggingOut();
        authenticationUtil().logout();
    }

    @POST // TODO: should be a PUT !?!?
    @Consumes(APPLICATION_JSON_CQRS_COMMAND)
    @Path("")
    public void sendForgotPutCommand(
            @HeaderParam("Content-type") String contentType,
            @QueryParam("command") @DefaultValue("") String queryCommandName,
            String commandJSON
    ) {
        LOG.info("AuthenticationResource PUT command");
        sendAndWait(contentType, queryCommandName, commandJSON,
                SendResetPasswordInstructions.class
        );
    }

    private void reportWrongUsernamePassword(CredentialsDTO credentials) {
        LOG.debug("User {} not logged in (wrong username / password)", credentials.username);
        logService().reportWrongUsernamePassword(credentials.username);
    }

    private void reportUserLoggedIn(String username) {
        LOG.debug("User {} logged in", username);
        logService().reportUserLoggedIn(username);
    }

    private void reportCurrentSubjectNotAuthenticated(java.lang.Exception e) {
        LOG.debug("Current user is not authenticated: ", e);
        logService().reportDebugError("Current user is not authenticated: ", e);
    }

    @GET
    @Path("/sink.html")
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public String sinkSubmitsFromPasswordManagers() {
        // dummy sink to consume "form submits" from browser password managers
        return "";
    }

    @POST
    @Path("/sink.html")
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public String sinkSubmitsFromPasswordManagersPost() {
        // dummy sink to consume "form submits" from browser password managers
        return "";
    }

    @GET
    @Path("/currentsubject")
    @Produces(MediaType.APPLICATION_JSON)
    public SubjectDTO currentSubject() {

        try {
            String userIdentifier = authenticationUtil().getUserId();

            // Lookup user
            Identity identity = identityService().findByUserId(userIdentifier);
            assertUserFound(identity);
            return createSubject(identity);
        } catch (UserNotLoggedInException | UnknownAccountException e) {
            reportCurrentSubjectNotAuthenticated(e);
            throw new UserNotAuthenticated();
        }
    }

    private void assertUserFound(Identity identity) throws UnknownAccountException {
        if (identity == null) {
            throw new UnknownAccountException("No user found in application registry");
        }
    }

    private SubjectDTO createSubject(Identity identity) {
        return new SubjectDTO(identity.username(), extractRolenamesAsArrayOfStrings(identity));
    }

    private String[] extractRolenamesAsArrayOfStrings(Identity identity) {
        return identity.username().equalsIgnoreCase("admin")
                ? new String[]{Role.USER.name(), Role.ADMIN.name()}
                : new String[]{Role.USER.name()};
    }

    /**
     * This is the representation of the logged in user as sent back to the clients
     */
    public static class SubjectDTO {

        private String username;
        private String[] roles;

        public SubjectDTO() {
        }

        public SubjectDTO(String username, String[] roles) {
            this.username = username;
            this.roles = roles;
        }

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

        public String[] getRoles() {
            return roles;
        }

        public void setRoles(String[] roles) {
            this.roles = roles;
        }
    }

    public static class CredentialsDTO {

        String username;
        String password;
        String emailAddress;
        String verificationId;

        public CredentialsDTO() {
        }

        public CredentialsDTO(String username, String password) {
            this.username = username;
            this.password = password;
        }

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

        public String getPassword() {
            return password;
        }

        public void setPassword(String password) {
            this.password = password;
        }

        public String getEmailAddress() {
            return emailAddress;
        }

        public void setEmailAddress(String emailAddress) {
            this.emailAddress = emailAddress;
        }

        public String getVerificationId() {
            return verificationId;
        }

        public void setVerificationId(String verificationId) {
            this.verificationId = verificationId;
        }

        @Override
        public String toString() {
            return "CredentialsDTO{" + "username=" + username + ", password=" + password + '}';
        }

    }

    public static class UserNotAuthenticated extends WebApplicationException {

        public UserNotAuthenticated() {
            super(Response.Status.UNAUTHORIZED);
        }
    }

}