package org.synyx.urlaubsverwaltung.security.oidc;

import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
import org.synyx.urlaubsverwaltung.person.Person;
import org.synyx.urlaubsverwaltung.person.PersonService;
import org.synyx.urlaubsverwaltung.person.Role;
import org.synyx.urlaubsverwaltung.security.PersonSyncService;

import java.util.Collection;
import java.util.Optional;

import static java.util.stream.Collectors.toList;
import static org.synyx.urlaubsverwaltung.person.Role.INACTIVE;

/**
 * @author Florian Krupicka - krupicka@synyx.de
 */
public class OidcPersonAuthoritiesMapper implements GrantedAuthoritiesMapper {

    private final PersonService personService;
    private final PersonSyncService personSyncService;

    public OidcPersonAuthoritiesMapper(PersonService personService, PersonSyncService personSyncService) {

        this.personService = personService;
        this.personSyncService = personSyncService;
    }

    @Override
    public Collection<? extends GrantedAuthority> mapAuthorities(Collection<? extends GrantedAuthority> authorities) {

        final Optional<? extends GrantedAuthority> authority = authorities.stream().findFirst();

        return authority.map(this::mapAuthorities).orElseThrow(RuntimeException::new); // TODO
    }

    private Collection<? extends GrantedAuthority> mapAuthorities(GrantedAuthority grantedAuthority) {
        if (grantedAuthority instanceof OidcUserAuthority) {

            final OidcUserAuthority oidcUserAuthority = (OidcUserAuthority) grantedAuthority;
            final Optional<String> firstName = extractGivenName(oidcUserAuthority);
            final Optional<String> lastName = extractFamilyName(oidcUserAuthority);
            final Optional<String> mailAddress = extractMailAddress(oidcUserAuthority);

            final String userUniqueID = oidcUserAuthority.getIdToken().getSubject();

            final Optional<Person> maybePerson = personService.getPersonByLogin(userUniqueID);

            final Person person;

            if (maybePerson.isPresent()) {
                person = personSyncService.syncPerson(maybePerson.get(), firstName, lastName, mailAddress);

                if (person.hasRole(INACTIVE)) {
                    throw new DisabledException("User '" + person.getId() + "' has been deactivated");
                }

            } else {
                final Person createdPerson = personSyncService.createPerson(userUniqueID, firstName, lastName, mailAddress);
                person = personSyncService.appointAsOfficeUserIfNoOfficeUserPresent(createdPerson);
            }

            return person.getPermissions()
                .stream()
                .map(Role::name)
                .map(SimpleGrantedAuthority::new)
                .collect(toList());
        } else {
            throw new OidcPersonMappingException("oidc: The granted authority was not a 'OidcUserAuthority' and the user cannot be mapped.");
        }
    }


    private Optional<String> extractFamilyName(OidcUserAuthority authority) {

        final Optional<String> familyName = Optional.ofNullable(authority.getIdToken().getFamilyName());
        if (familyName.isPresent()) {
            return familyName;
        } else {
            return Optional.ofNullable(authority.getUserInfo().getFamilyName());
        }
    }

    private Optional<String> extractGivenName(OidcUserAuthority authority) {

        final Optional<String> givenName = Optional.ofNullable(authority.getIdToken().getGivenName());
        if (givenName.isPresent()) {
            return givenName;
        } else {
            return Optional.ofNullable(authority.getUserInfo().getGivenName());
        }

    }

    private Optional<String> extractMailAddress(OidcUserAuthority authority) {

        final Optional<String> mailAddress = Optional.ofNullable(authority.getIdToken().getEmail());
        if (mailAddress.isPresent()) {
            return mailAddress;
        } else {
            return Optional.ofNullable(authority.getUserInfo().getEmail());
        }
    }
}

@@ -1398,83 +1398,118 @@
     
-final Optional%3C? extends GrantedAuthority%3E authority = authorities.stream()
+return authorities%0A            .stream()%0A            .filter(OidcUserAuthority.class::isInstance)%0A            
 .fin
@@ -1516,27 +1516,27 @@
 dFirst()
-;%0A%0A
+%0A  
         
 return a
@@ -1531,301 +1531,310 @@
     
-return authority.map(this::mapAuthorities).orElseThrow(RuntimeException::new); // TODO%0A    %7D%0A%0A    private Collection%3C? extends GrantedAuthority%3E mapAuthorities(GrantedAuthority grantedAuthority) %7B
+  .map(OidcUserAuthority.class::cast)%0A            .map(this::mapAuthorities)%0A            .orElseThrow(() -%3E new OidcPersonMappingException(%22oidc: The granted authority was not a 'OidcUserAuthority' and the user cannot be mapped.%22));
 %0A    
+%7D%0A%0A
     
-if (grantedAuthority instanceof OidcUserAuthority) %7B%0A%0A            final OidcUser
+private Collection%3C? extends GrantedAuthority%3E map
 Authorit
 y oi
@@ -1833,11 +1833,13 @@
 orit
-y o
+ies(O
 idcU
@@ -1855,12 +1855,9 @@
 ity 
-= (O
+o
 idcU
@@ -1874,25 +1874,10 @@
 ty) 
-grantedAuthority;
+%7B%0A
 %0A   
@@ -3121,177 +3121,8 @@
 ));%0A
-        %7D else %7B%0A            throw new OidcPersonMappingException(%22oidc: The granted authority was not a 'OidcUserAuthority' and the user cannot be mapped.%22);%0A        %7D%0A
     

Handle creation of granted authorities more elegantly