// access_token 처리, refresh_token 처리
  // grant_type이 password 와 client_credentials 인 경우는 if 문 블럭만 작성하였음. 추후 작성해야 함.
  @RequestMapping(value = "token")
  public String accessToken(RequestAccessTokenVO ratVO, Model model, HttpServletRequest request)
      throws OAuth2Exception {

    String json = "";

    // grant type이 무엇이냐에 따라 각기 다른 처리
    // password, client_credential은 미구현.. 추후 구현 요망됨.
    System.out.println("### token flow 1");
    System.out.println("### grant_type : " + ratVO.getGrant_type());

    if (ratVO.getGrant_type().equals(OAuth2Constant.GRANT_TYPE_AUTHORIZATION_CODE)) {
      ResponseAccessTokenVO resVO = accessTokenServerFlow(ratVO, request);
      json = OAuth2Util.getJSONFromObject(resVO);
    } else if (ratVO.getGrant_type().equals(OAuth2Constant.GRANT_TYPE_PASSWORD)) {
      // 차후 구현할 것
      throw new OAuth2Exception(500, OAuth2ErrorConstant.UNSUPPORTED_RESPONSE_TYPE);
    } else if (ratVO.getGrant_type().equals(OAuth2Constant.GRANT_TYPE_CLIENT_CREDENTIALS)) {
      // 차후 구현할 것
      throw new OAuth2Exception(500, OAuth2ErrorConstant.UNSUPPORTED_RESPONSE_TYPE);
    } else if (ratVO.getGrant_type().equals(OAuth2Constant.GRANT_TYPE_REFRESH_TOKEN)) {
      // refresh token 사용 여부에 따라 값이 달라짐.
      if (OAuth2Constant.USE_REFRESH_TOKEN) {
        ResponseAccessTokenVO resVO = refreshTokenFlow(ratVO, request);
        json = OAuth2Util.getJSONFromObject(resVO);
      } else {
        throw new OAuth2Exception(500, OAuth2ErrorConstant.UNSUPPORTED_RESPONSE_TYPE);
      }
    } else {
      throw new OAuth2Exception(500, OAuth2ErrorConstant.UNSUPPORTED_RESPONSE_TYPE);
    }
    model.addAttribute("json", json);
    return "json/json";
  }
  // grant_type이 authorization_code일 때
  private ResponseAccessTokenVO refreshTokenFlow(
      RequestAccessTokenVO ratVO, HttpServletRequest request) throws OAuth2Exception {
    // 1. 전달된 refresh Token과
    // GET 방식일 때는 Client ID와 Client Secret은 Authorization Header를 통해 전달되어야 함.
    if (request.getMethod().equalsIgnoreCase("GET")) {
      String authHeader = (String) request.getHeader("Authorization");
      if (authHeader == null || authHeader.equals("")) {
        throw new OAuth2Exception(400, OAuth2ErrorConstant.INVALID_PARAMETER);
      }
      // Basic 인증 헤더 파싱
      OAuth2Util.parseBasicAuthHeader(authHeader, ratVO);
    }

    // 2. ClientID, Secret 모두 전달되었는지 여부 --> 존재 여부 확인
    if (ratVO.getClient_id() == null || ratVO.getClient_secret() == null) {
      throw new OAuth2Exception(400, OAuth2ErrorConstant.INVALID_PARAMETER);
    }

    // 3. clientID 와 client_secret의 일치여부
    ClientVO cVOTemp = new ClientVO();
    cVOTemp.setClient_id(ratVO.getClient_id());
    ClientVO cVO = null;
    try {
      cVO = dao.getClientOne(cVOTemp);
    } catch (Exception e) {
      throw new OAuth2Exception(500, OAuth2ErrorConstant.SERVER_ERROR);
    }

    if (cVO == null) {
      throw new OAuth2Exception(500, OAuth2ErrorConstant.UNAUTHORIZED_CLIENT);
    }

    if (ratVO.getClient_secret() != null
        && !cVO.getClient_secret().equals(ratVO.getClient_secret())) {
      throw new OAuth2Exception(500, OAuth2ErrorConstant.UNAUTHORIZED_CLIENT);
    }

    // 4. refresh token의 일치 여부
    if (ratVO.getRefresh_token() == null) {
      throw new OAuth2Exception(400, OAuth2ErrorConstant.INVALID_PARAMETER);
    }

    TokenVO tVOTemp = new TokenVO();
    tVOTemp.setRefresh_token(ratVO.getRefresh_token());
    TokenVO tVO = null;
    try {
      tVO = dao.selectRefreshToken(tVOTemp);
    } catch (Exception e) {
      e.printStackTrace();
      throw new OAuth2Exception(500, OAuth2ErrorConstant.SERVER_ERROR);
    }

    if (tVO == null) {
      throw new OAuth2Exception(400, OAuth2ErrorConstant.INVALID_TOKEN);
    }

    // 5. TokenVO의 accessToken 갱신 --> DB 업데이트 -->
    // --> refreshToken 값 없이  ResponseAccessTokenVO객체 생성 --> JSON 포맷으로 응답
    tVO.setAccess_token(OAuth2Util.generateToken());
    tVO.setCreated_at(OAuth2Util.getCurrentTimeStamp());
    try {
      dao.updateAccessToken(tVO);
    } catch (Exception e) {
      e.printStackTrace();
      throw new OAuth2Exception(500, OAuth2ErrorConstant.SERVER_ERROR);
    }

    ResponseAccessTokenVO resVO =
        new ResponseAccessTokenVO(
            tVO.getAccess_token(),
            tVO.getToken_type(),
            tVO.getExpires_in(),
            null,
            ratVO.getState(),
            tVO.getCreated_at());

    return resVO;
  }
  // grant_type이 authorization_code일 때
  private ResponseAccessTokenVO accessTokenServerFlow(
      RequestAccessTokenVO ratVO, HttpServletRequest request) throws OAuth2Exception {

    // GET 방식일 때는 Client ID와 Client Secret은 Authorization Header를 통해 전달되어야 함.
    System.out.println("### token flow 2");
    if (request.getMethod().equalsIgnoreCase("GET")) {
      String authHeader = (String) request.getHeader("Authorization");
      if (authHeader == null || authHeader.equals("")) {
        throw new OAuth2Exception(400, OAuth2ErrorConstant.INVALID_PARAMETER);
      }
      // Basic 인증 헤더 파싱
      OAuth2Util.parseBasicAuthHeader(authHeader, ratVO);
    }

    // 1. ClientID, Secret 모두 전달되었는지 여부 --> 존재 여부 확인
    System.out.println("### token flow 3");
    if (ratVO.getClient_id() == null || ratVO.getClient_secret() == null) {
      throw new OAuth2Exception(400, OAuth2ErrorConstant.INVALID_PARAMETER);
    }

    ClientVO cVOTemp = new ClientVO();
    cVOTemp.setClient_id(ratVO.getClient_id());
    cVOTemp.setClient_secret(ratVO.getClient_secret());
    ClientVO cVO = null;
    try {
      cVO = dao.getClientOne(cVOTemp);
    } catch (Exception e) {
      e.printStackTrace();
      throw new OAuth2Exception(500, OAuth2ErrorConstant.SERVER_ERROR);
    }

    // 1.2 client 존재 여부 확인
    System.out.println("### token flow 4");
    if (cVO == null) {
      throw new OAuth2Exception(401, OAuth2ErrorConstant.UNAUTHORIZED_CLIENT);
    }

    // 2. redirect_uri 일치 여부 확인
    System.out.println("### token flow 5");
    if (!ratVO.getRedirect_uri().equals(cVO.getRedirect_uri())) {
      throw new OAuth2Exception(400, OAuth2ErrorConstant.NOT_MATCH_REDIRECT_URI);
    }

    // 3. code값 일치 여부 확인
    System.out.println("### token flow 6");
    if (ratVO.getCode() == null) {
      throw new OAuth2Exception(400, OAuth2ErrorConstant.INVALID_PARAMETER);
    }

    TokenVO tVOTemp = new TokenVO();
    tVOTemp.setCode(ratVO.getCode());
    TokenVO tVO = null;
    try {
      tVO = dao.selectTokenByCode(tVOTemp);
    } catch (Exception e) {
      e.printStackTrace();
      throw new OAuth2Exception(500, OAuth2ErrorConstant.SERVER_ERROR);
    }

    if (tVO == null) {
      throw new OAuth2Exception(400, OAuth2ErrorConstant.INVALID_CODE);
    }

    // 4. expire되었는지 여부 확인, refresh token 사용 여부에 따라 결정함.
    if (OAuth2Constant.USE_REFRESH_TOKEN) {
      System.out.println("### token flow 7");
      long expires = tVO.getCreated_at() + tVO.getExpires_in();
      if (System.currentTimeMillis() > expires) {
        // 토큰 생성되고 한참 뒤에 code로 access token을 요청하는 것은 금지
        throw new OAuth2Exception(400, OAuth2ErrorConstant.EXPIRED_TOKEN);
      }
    }

    // 5. 전달받은 state값 그대로 전달(CSRF 공격 방지용)
    // 6. ResponseAccessToken객체 생성 --> json 포맷 응답
    System.out.println("### token flow 9");
    ResponseAccessTokenVO resVO = new ResponseAccessTokenVO();

    resVO.setIssued_at(tVO.getCreated_at());
    resVO.setState(ratVO.getState());
    resVO.setToken_type(tVO.getToken_type());
    if (OAuth2Constant.USE_REFRESH_TOKEN) {
      resVO.setAccess_token(tVO.getAccess_token());
      resVO.setExpires_in(tVO.getExpires_in());
      resVO.setRefresh_token(tVO.getRefresh_token());
    } else {
      // 6.1. password를 확보하기 위해 UserVO 객체 획득
      //     ResponsToken을 생성하고 나면 token 테이블의 레코드 삭제
      UserVO uVOTemp = new UserVO();
      uVOTemp.setUserid(tVO.getUserid());
      UserVO uVO = null;
      try {
        uVO = dao.getUserInfo(uVOTemp);
      } catch (Exception e) {
        e.printStackTrace();
        throw new OAuth2Exception(500, OAuth2ErrorConstant.SERVER_ERROR);
      }

      if (uVO == null) {
        throw new OAuth2Exception(500, OAuth2ErrorConstant.INVALID_USER);
      }

      // token 테이블 레코드 삭제
      try {
        dao.deleteToken(tVO);
      } catch (Exception e) {
        e.printStackTrace();
        throw new OAuth2Exception(500, OAuth2ErrorConstant.SERVER_ERROR);
      }

      resVO.setAccess_token(
          this.tokenService.generateAccessToken(
              cVO.getClient_id(), cVO.getClient_secret(), uVO.getUserid(), uVO.getPassword()));
    }

    return resVO;
  }