/*
 * Copyright (c) 2020 yifu4cloud Authors. All Rights Reserved.
 *
 * 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 com.yifu.cloud.plus.v1.yifu.auth.endpoint;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yifu.cloud.plus.v1.yifu.admin.api.dto.UserInfo;
import com.yifu.cloud.plus.v1.yifu.admin.api.entity.SysUser;
import com.yifu.cloud.plus.v1.yifu.auth.util.LoginTool;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.callback.WeixinLoginCallback;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.model.WeixinError;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.model.WeixinUserInfo;
import com.yifu.cloud.plus.v1.yifu.auth.vo.WxUserVo;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.CacheConstants;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.CommonConstants;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.SecurityConstants;
import com.yifu.cloud.plus.v1.yifu.common.core.util.Common;
import com.yifu.cloud.plus.v1.yifu.common.core.util.R;
import com.yifu.cloud.plus.v1.yifu.common.core.util.SpringContextHolder;
import com.yifu.cloud.plus.v1.yifu.common.core.vo.YifuUser;
import com.yifu.cloud.plus.v1.yifu.common.dapr.config.DaprUpmsProperties;
import com.yifu.cloud.plus.v1.yifu.common.dapr.util.HttpDaprUtil;
import com.yifu.cloud.plus.v1.yifu.common.security.annotation.Inner;
import com.yifu.cloud.plus.v1.yifu.common.security.util.AuthUtils;
import com.yifu.cloud.plus.v1.yifu.common.security.util.SecurityUtils;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.event.LogoutSuccessEvent;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestValidator;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author lengleng
 * @date 2019/2/1 删除token端点
 */
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/token")
@EnableConfigurationProperties(DaprUpmsProperties.class)
@Tag(name = "获取Token")
public class YifuTokenEndpoint {

	private final DaprUpmsProperties daprUpmsProperties;

	private final ClientDetailsService clientDetailsService;

	private final TokenStore tokenStore;

	private final RedisTemplate redisTemplate;

	private final CacheManager cacheManager;

	private final PasswordEncoder passwordEncoder;

	private final AuthorizationServerTokenServices defaultAuthorizationServerTokenServices;

	/**
	 * 构建userdetails
	 *
	 * @param result 用户信息
	 * @return
	 */
	private UserDetails getUserDetails(R<UserInfo> result) {
		if (result == null || result.getData() == null) {
			throw new UsernameNotFoundException("用户不存在");
		}
		UserInfo info = result.getData();
		// 获取资源
		Set<String> dbAuthsSet = new HashSet<>(Arrays.asList(info.getPermissions()));
		Collection<? extends GrantedAuthority> authorities
				= AuthorityUtils.createAuthorityList(dbAuthsSet.toArray(new String[0]));
		SysUser user = info.getSysUser();
		// 构造security用户
		YifuUser thisUser = new YifuUser(user.getUserId(), user.getDeptId(),user.getDeptName(), user.getUsername(),
				user.getNickname(),user.getSystemFlag(), SecurityConstants.BCRYPT + user.getPassword(),
				user.getPhone(), true, true, true,
				CommonConstants.STATUS_NORMAL.equals(user.getLockFlag()),
				user.getUserGroup(),authorities, user.getLdapDn(),
				info.getClientRoleMap(),info.getSettleIdList(),
				user.getType());
		thisUser.setClientRoleMap(info.getClientRoleMap());
		return  thisUser;
	}

	/**
	 * 认证页面
	 * @param modelAndView
	 * @param error 表单登录失败处理回调的错误信息
	 * @return ModelAndView
	 */
	@GetMapping("/login")
	public ModelAndView require(ModelAndView modelAndView, @RequestParam(required = false) String error) {
		modelAndView.setViewName("ftl/login");
		modelAndView.addObject("error", error);
		return modelAndView;
	}

	/**
	 * 确认授权页面
	 * @param request
	 * @param session
	 * @param modelAndView
	 * @return
	 */
	@GetMapping("/confirm_access")
	public ModelAndView confirm(HttpServletRequest request, HttpSession session, ModelAndView modelAndView) {
		Map<String, Object> scopeList = (Map<String, Object>) request.getAttribute("scopes");
		modelAndView.addObject("scopeList", scopeList.keySet());

		Object auth = session.getAttribute("authorizationRequest");
		if (auth != null) {
			AuthorizationRequest authorizationRequest = (AuthorizationRequest) auth;
			ClientDetails clientDetails = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId());
			modelAndView.addObject("app", clientDetails.getAdditionalInformation());
			modelAndView.addObject("user", SecurityUtils.getUser());
		}

		modelAndView.setViewName("ftl/confirm");
		return modelAndView;
	}

	/**
	 * 退出并删除token
	 * @param authHeader Authorization
	 */
	@DeleteMapping("/logout")
	public R<Boolean> logout(@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authHeader) {
		if (StrUtil.isBlank(authHeader)) {
			return R.ok();
		}

		String tokenValue = authHeader.replace(OAuth2AccessToken.BEARER_TYPE, StrUtil.EMPTY).trim();
		return removeToken(tokenValue);
	}

	/**
	 * 令牌管理调用
	 * @param token token
	 */
	@Inner
	@DeleteMapping("/{token}")
	public R<Boolean> removeToken(@PathVariable("token") String token) {
		OAuth2AccessToken accessToken = tokenStore.readAccessToken(token);
		if (accessToken == null || StrUtil.isBlank(accessToken.getValue())) {
			return R.ok();
		}

		OAuth2Authentication auth2Authentication = tokenStore.readAuthentication(accessToken);
		// 清空用户信息
		cacheManager.getCache(CacheConstants.USER_DETAILS).evict(auth2Authentication.getName());

		// 清空access token
		tokenStore.removeAccessToken(accessToken);

		// 清空 refresh token
		OAuth2RefreshToken refreshToken = accessToken.getRefreshToken();
		tokenStore.removeRefreshToken(refreshToken);

		// 处理自定义退出事件，保存相关日志
		SpringContextHolder.publishEvent(new LogoutSuccessEvent(auth2Authentication));
		return R.ok();
	}

	/**
	 * 查询token
	 * @param params 分页参数
	 * @return
	 */
	@Inner
	@PostMapping("/page")
	public R<Page> tokenList(@RequestBody Map<String, Object> params) {
		// 根据分页参数获取对应数据
		String key = String.format("%sauth_to_access:*", CacheConstants.PROJECT_OAUTH_ACCESS);
		int current = MapUtil.getInt(params, CommonConstants.CURRENT);
		int size = MapUtil.getInt(params, CommonConstants.SIZE);
		Set<String> keys = redisTemplate.keys(key);
		List<String> pages = keys.stream().skip((current - 1) * size).limit(size).collect(Collectors.toList());
		Page result = new Page(current, size);
		result.setRecords(redisTemplate.opsForValue().multiGet(pages));
		result.setTotal(keys.size());
		return R.ok(result);
	}

	/**
	 * 微信扫码回调登录业务处理
	 * @param code
	 * @param state 客户端加密串
	 * @param type 0 或空 取值配置  1  取值传参
	 * @return
	 */
	@GetMapping(value = "/weixin/callback")
	@ResponseBody
	public String wxCallback(HttpServletRequest request,@RequestParam String code, String state, String type, String appid , String secret) {
		return LoginTool.loginTwo(code, appid, secret, getCallback(request, state));
	}

	private WeixinLoginCallback getCallback(HttpServletRequest request, String state) {
		return new WeixinLoginCallback() {
			@Override
			public String handle(WeixinUserInfo userInfo) {
				if (null != userInfo && null != userInfo.getOpenid()) {
					SysUser user = new SysUser();
					user.setWxOpenid(userInfo.getOpenid());
					user.setWxNickName(userInfo.getNickname());
					R<UserInfo> result = HttpDaprUtil.invokeMethodPost(daprUpmsProperties.getAppUrl(), daprUpmsProperties.getAppId()
							, "/user/inner/getSysUserByWxOpenId", user, UserInfo.class, SecurityConstants.FROM_IN);
					OAuth2AccessToken oAuth2AccessToken = null;
					if (null != result && null != result.getData() && result.getData().getSysUser() != null) {
						SysUser sysUser = result.getData().getSysUser();
						if (!CommonConstants.ZERO_STRING.equals(sysUser.getLockFlag())) {
							return JSON.toJSONString(R.failed("用户".concat(sysUser.getNickname()).concat("账号：").concat(sysUser.getUsername()).concat("已被锁定,请联系管理员。")));
						}
						UserDetails userDetails = getUserDetails(result);
						try {
							String header = request.getHeader(com.yifu.cloud.plus.v1.yifu.auth.constants.SecurityConstants.CLIENT_HEADER_KEY_NEW);
							if(Common.isEmpty(header)){
								header = request.getHeader(com.yifu.cloud.plus.v1.yifu.auth.constants.SecurityConstants.CLIENT_HEADER_KEY);
								//兼容老逻辑
								if(Common.isEmpty(header)){
									header = request.getHeader(HttpHeaders.AUTHORIZATION);
								}
							}
							if (Common.isEmpty(header)) {
								throw new InvalidClientException("请求头中client信息为空");
							}
							String[] tokens = AuthUtils.extractAndDecodeHeader(header);
							assert tokens.length == 2;
							String clientId = tokens[0];
							ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
							//校验secret
							if (!passwordEncoder.matches(tokens[1], clientDetails.getClientSecret())) {
								throw new InvalidClientException("Given client ID does not match authenticated client");

							}
							TokenRequest tokenRequest = new TokenRequest(MapUtil.newHashMap(), clientId, clientDetails.getScope(), "mobile");

							//校验scope
							new DefaultOAuth2RequestValidator().validateScope(tokenRequest, clientDetails);
							OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
							UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken
									(userDetails, userDetails.getPassword());
							OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
							oAuth2AccessToken = defaultAuthorizationServerTokenServices.createAccessToken(oAuth2Authentication);
						} catch (Exception e) {
							log.info("客户端解析出错。");
							return JSON.toJSONString(R.failed("无此客户端"));
						}
						WxUserVo wxUserVo = new WxUserVo();
						wxUserVo.setOauth2AccessToken(oAuth2AccessToken);
						wxUserVo.setWeixinUserInfo(userInfo);
						wxUserVo.setUser(sysUser);
						return JSON.toJSONString(R.ok(wxUserVo));
					} else {
						return JSON.toJSONString(R.other(userInfo, "该用户未注册", CommonConstants.REGISTERED));
					}
				}
				//查询用户
				log.info("登录成功业务处理");
				return JSON.toJSONString(R.failed("openid为空"));
			}

			@Override
			public String errer(WeixinError error) {
				log.info("登录失败业务处理");
				log.error(error.getErrmsg());
				return JSON.toJSONString(R.failed(error.getErrmsg()));
			}
		};
	}

}
