package org.lingniu.idp.config;

import org.lingniu.idp.enums.CustomScopes;
import org.lingniu.idp.model.dto.LoginUser;
import org.lingniu.idp.model.entity.SysUser;
import org.lingniu.idp.service.core.SysPermissionService;
import org.lingniu.idp.service.core.login.LoginService;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.function.Function;

@Component
public class OidcUserInfoMapper
			implements Function<OidcUserInfoAuthenticationContext, OidcUserInfo> {

		private final LoginService loginService;


		// @formatter:off
		private static final List<String> EMAIL_CLAIMS = Arrays.asList(
				StandardClaimNames.EMAIL,
				StandardClaimNames.EMAIL_VERIFIED
		);
		private static final List<String> PHONE_CLAIMS = Arrays.asList(
				StandardClaimNames.PHONE_NUMBER,
				StandardClaimNames.PHONE_NUMBER_VERIFIED
		);
		private static final List<String> PROFILE_CLAIMS = Arrays.asList(
				StandardClaimNames.NAME,
				StandardClaimNames.FAMILY_NAME,
				StandardClaimNames.GIVEN_NAME,
				StandardClaimNames.MIDDLE_NAME,
				StandardClaimNames.NICKNAME,
				StandardClaimNames.PREFERRED_USERNAME,
				StandardClaimNames.PROFILE,
				StandardClaimNames.PICTURE,
				StandardClaimNames.WEBSITE,
				StandardClaimNames.GENDER,
				StandardClaimNames.BIRTHDATE,
				StandardClaimNames.ZONEINFO,
				StandardClaimNames.LOCALE,
				StandardClaimNames.UPDATED_AT
		);

		public OidcUserInfoMapper(LoginService loginService) {this.loginService = loginService;}
		// @formatter:on

		@Override
		public OidcUserInfo apply(OidcUserInfoAuthenticationContext authenticationContext) {
			OAuth2Authorization authorization = authenticationContext.getAuthorization();
			OidcIdToken idToken = authorization.getToken(OidcIdToken.class).getToken();
			OAuth2AccessToken accessToken = authenticationContext.getAccessToken();
			Map<String, Object> scopeRequestedClaims = getClaimsRequestedByScope(idToken.getClaims(),
					accessToken.getScopes());
			String userName = authorization.getPrincipalName();
			SysUser sysUser = loginService.getUserDetail(userName);
			scopeRequestedClaims.put("userId",sysUser.getUserId());
			scopeRequestedClaims.put("currentDeptId",sysUser.getDeptId());
			scopeRequestedClaims.put("username",sysUser.getUserName());
			scopeRequestedClaims.put("userDepts",sysUser.getDeptList());
			scopeRequestedClaims.put("userPosts",sysUser.getPosts());
			scopeRequestedClaims.put("nickName",sysUser.getNickName());
			scopeRequestedClaims.put("sex",sysUser.getSex());
			if(accessToken.getScopes().contains(CustomScopes.PERMS.name().toLowerCase())){
				Map<String, Object> permissionInfo = loginService.getPermissionInfo(sysUser);
				scopeRequestedClaims.putAll(permissionInfo);

			}
			return new OidcUserInfo(scopeRequestedClaims);
		}

		private static Map<String, Object> getClaimsRequestedByScope(Map<String, Object> claims,
				Set<String> requestedScopes) {
			Set<String> scopeRequestedClaimNames = new HashSet<>(32);
			scopeRequestedClaimNames.add(StandardClaimNames.SUB);

			if (requestedScopes.contains(OidcScopes.ADDRESS)) {
				scopeRequestedClaimNames.add(StandardClaimNames.ADDRESS);
			}
			if (requestedScopes.contains(OidcScopes.EMAIL)) {
				scopeRequestedClaimNames.addAll(EMAIL_CLAIMS);
			}
			if (requestedScopes.contains(OidcScopes.PHONE)) {
				scopeRequestedClaimNames.addAll(PHONE_CLAIMS);
			}
			if (requestedScopes.contains(OidcScopes.PROFILE)) {
				scopeRequestedClaimNames.addAll(PROFILE_CLAIMS);
			}



			Map<String, Object> requestedClaims = new HashMap<>(claims);
			requestedClaims.keySet().removeIf((claimName) -> !scopeRequestedClaimNames.contains(claimName));

			return requestedClaims;
		}

	}