package com.oguerreiro.resilient.security;

import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Stream;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;

import com.oguerreiro.resilient.domain.Organization;
import com.oguerreiro.resilient.security.custom.ResilientUserDetails;
import com.oguerreiro.resilient.security.resillient.SecurityGroup;

/**
 * Utility class for Spring Security.
 */
public final class SecurityUtils {

  private SecurityUtils() {
  }

  /**
   * Get the login of the current user.
   *
   * @return the login of the current user.
   */
  public static Optional<String> getCurrentUserLogin() {
    SecurityContext securityContext = SecurityContextHolder.getContext();
    return Optional.ofNullable(extractPrincipal(securityContext.getAuthentication()));
  }

  private static String extractPrincipal(Authentication authentication) {
    if (authentication == null) {
      return null;
    } else if (authentication.getPrincipal() instanceof UserDetails springSecurityUser) {
      return springSecurityUser.getUsername();
    } else if (authentication.getPrincipal() instanceof String s) {
      return s;
    }
    return null;
  }

  /**
   * Check if a user is authenticated.
   *
   * @return true if the user is authenticated, false otherwise.
   */
  public static boolean isAuthenticated() {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    return authentication != null && getAuthorities(authentication).noneMatch(AuthoritiesConstants.ANONYMOUS::equals);
  }

  /**
   * Checks if the current user has any of the authorities.
   *
   * @param  authorities the authorities to check.
   * @return             true if the current user has any of the authorities, false otherwise.
   */
  public static boolean hasCurrentUserAnyOfAuthorities(String... authorities) {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    return (authentication != null
        && getAuthorities(authentication).anyMatch(authority -> Arrays.asList(authorities).contains(authority)));
  }

  /**
   * Checks if the current user has none of the authorities.
   *
   * @param  authorities the authorities to check.
   * @return             true if the current user has none of the authorities, false otherwise.
   */
  public static boolean hasCurrentUserNoneOfAuthorities(String... authorities) {
    return !hasCurrentUserAnyOfAuthorities(authorities);
  }

  /**
   * Checks if the current user has a specific authority.
   *
   * @param  authority the authority to check.
   * @return           true if the current user has the authority, false otherwise.
   */
  public static boolean hasCurrentUserThisAuthority(String authority) {
    return hasCurrentUserAnyOfAuthorities(authority);
  }

  public static SecurityGroup getCurrentUserSecurityGroup() {
    if (!isAuthenticated()) {
      return null;
    }

    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    Object details = authentication.getPrincipal();
    if (details == null || !(details instanceof ResilientUserDetails)) {
      return null;
    }
    ResilientUserDetails resilientDetails = (ResilientUserDetails) details;

    return resilientDetails.getSecurityGroup();
  }

  public static Organization getCurrentUserParentOrganization() {
    if (!isAuthenticated()) {
      return null;
    }

    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    Object details = authentication.getPrincipal();
    if (details == null || !(details instanceof ResilientUserDetails)) {
      return null;
    }
    ResilientUserDetails resilientDetails = (ResilientUserDetails) details;

    return resilientDetails.getParentOrganization();
  }

  private static Stream<String> getAuthorities(Authentication authentication) {
    return authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority);
  }
}
