package <%=packageName%>.web.rest.standalone;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import <%=packageName%>.security.MyUserDetails;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import <%=packageName%>.domain.User;
import <%=packageName%>.security.jwt.JWTFilter;
import <%=packageName%>.security.jwt.TokenProvider;
import <%=packageName%>.service.UserService;
import <%=packageName%>.service.WxMaUserService;
import <%=packageName%>.service.dto.UserDTO;
import <%=packageName%>.service.dto.WxMaUserDTO;
import <%=packageName%>.service.standalone.WxMaConfigService;
import <%=packageName%>.web.rest.vm.WxCodeVM;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.*;

import java.time.ZonedDateTime;
import java.util.HashSet;
import java.util.Locale;
import java.util.Optional;

/**
 * 微信小程序用户接口
 *
 * @author <a href="https://github.com/binarywang">Binary Wang</a>
 */
@RestController
@RequestMapping("/wx/user/{appid}")
public class WxMaUserController {
    private final Logger log = LoggerFactory.getLogger(WxMaUserController.class);

    private final WxMaConfigService wxMaConfigService;
    private final ObjectMapper objectMapper = new ObjectMapper();

    private final UserService userService;
    private final UserDetailsService userDetailsService;
    private final WxMaUserService wxMaUserService;
    private final TokenProvider tokenProvider;

    public WxMaUserController(WxMaConfigService wxMaConfigService, UserService userService, UserDetailsService userDetailsService, WxMaUserService wxMaUserService, TokenProvider tokenProvider) {
        this.wxMaConfigService = wxMaConfigService;
        this.userService = userService;
        this.userDetailsService = userDetailsService;
        this.wxMaUserService = wxMaUserService;
        this.tokenProvider = tokenProvider;
    }

    @PostMapping("/authenticate")
    public ResponseEntity authorize(@PathVariable String appid, @RequestBody WxCodeVM wxCodeVM) {
        final WxMaService wxService = wxMaConfigService.getMaService(appid);

        try {
            WxMaJscode2SessionResult session = wxService.getUserService().getSessionInfo(wxCodeVM.getCode());
            String openid = session.getOpenid();
            Optional<User> userWithAuthoritiesByLogin = userService.getUserWithAuthoritiesByLogin(openid.toLowerCase(Locale.ENGLISH));
            if (!userWithAuthoritiesByLogin.isPresent()) {
                // 增加一个用户,密码随机
                UserDTO userDTO = new UserDTO();
                userDTO.setActivated(true);
                userDTO.setLogin(openid.toLowerCase(Locale.ENGLISH));
                userDTO.setCreatedBy("system");
                userDTO.setLastModifiedBy("system");
                userDTO.setEmail(openid.toLowerCase(Locale.ENGLISH) + "@wxmp.com");
                HashSet<String> authorities = new HashSet<>();
                authorities.add("ROLE_USER");
                userDTO.setAuthorities(authorities);
                User user = userService.createUser(userDTO);

                // 增加一个微信用户信息
                WxMaUserDTO wxMaUserDTO = new WxMaUserDTO();
                wxMaUserDTO.setAppid(appid);
                wxMaUserDTO.setCreateTime(ZonedDateTime.now());
                wxMaUserDTO.setOpenid(openid);
                wxMaUserDTO.setUnionid(session.getUnionid());
                wxMaUserDTO.setUserId(user.getId());
                wxMaUserService.save(wxMaUserDTO);
            }
            MyUserDetails user = (MyUserDetails) userDetailsService.loadUserByUsername(openid.toLowerCase(Locale.ENGLISH));
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                user,
                null,
                user.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            String jwt = tokenProvider.createTokenWithWx(authenticationToken, false,session.getSessionKey(), user.getId());
            HttpHeaders httpHeaders = new HttpHeaders();
            httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer " + jwt);
            return new ResponseEntity<>(new JWTToken(jwt), httpHeaders, HttpStatus.OK);
        } catch (Exception e) {
            this.log.error(e.getMessage(), e);
            return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
        }
    }

    /**
     * <pre>
     * 获取用户信息接口
     * </pre>
     */
    @GetMapping("/info")
    public String info(@PathVariable String appid, String sessionKey,
                       String signature, String rawData, String encryptedData, String iv) {
        final WxMaService wxService = wxMaConfigService.getMaService(appid);

        // 用户信息校验
        if (!wxService.getUserService().checkUserInfo(sessionKey, rawData, signature)) {
            return "user check failed";
        }

        // 解密用户信息
        WxMaUserInfo userInfo = wxService.getUserService().getUserInfo(sessionKey, encryptedData, iv);

        try {
            return objectMapper.writeValueAsString(userInfo);
        } catch (Exception e) {
            e.printStackTrace();
            return "user info to json failed";
        }
    }

    /**
     * <pre>
     * 获取用户绑定手机号信息
     * </pre>
     */
    @GetMapping("/phone")
    public String phone(@PathVariable String appid, String sessionKey, String signature,
                        String rawData, String encryptedData, String iv) {
        final WxMaService wxService = wxMaConfigService.getMaService(appid);

        // 用户信息校验
        if (!wxService.getUserService().checkUserInfo(sessionKey, rawData, signature)) {
            return "user check failed";
        }

        // 解密
        WxMaPhoneNumberInfo phoneNoInfo = wxService.getUserService().getPhoneNoInfo(sessionKey, encryptedData, iv);

        try {
            return objectMapper.writeValueAsString(phoneNoInfo);
        } catch (Exception e) {
            e.printStackTrace();
            return "user phoneNoInfo to json failed";
        }
    }

    /**
     * Object to return as body in JWT Authentication.
     */
    static class JWTToken {

        private String idToken;

        JWTToken(String idToken) {
            this.idToken = idToken;
        }

        @JsonProperty("id_token")
        String getIdToken() {
            return idToken;
        }

        void setIdToken(String idToken) {
            this.idToken = idToken;
        }
    }
}
