package com.yonge.cooleshow.auth.web.controller;

import com.alibaba.fastjson.JSONObject;
import com.google.gson.JsonObject;
import com.yonge.cooleshow.auth.api.dto.QRLoginDto;
import com.yonge.cooleshow.common.enums.CacheNameEnum;
import com.yonge.cooleshow.common.util.TokenUtil;
import com.yonge.toolset.base.util.StringUtil;
import io.swagger.annotations.*;

import java.io.IOException;
import java.util.Base64;
import java.util.Calendar;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.yonge.cooleshow.auth.api.entity.SysUser;
import com.yonge.cooleshow.auth.core.service.CustomTokenServices;
import com.yonge.cooleshow.auth.service.SysUserService;
import com.yonge.cooleshow.common.controller.BaseController;
import com.yonge.cooleshow.common.entity.HttpResponseResult;
import com.yonge.cooleshow.common.security.AuthUser;
import com.yonge.cooleshow.common.security.SecurityUtils;

@RestController
@Api(tags = "认证服务")
public class TokenController extends BaseController {

    @Autowired
    private CustomTokenServices tokenService;

    @Autowired
    private ResourceServerTokenServices resourceServerTokenServices;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private SysUserService userService;

    @Autowired
    private RedissonClient redissonClient;

    @PostMapping(value = "/smsLogin", consumes = MediaType.APPLICATION_JSON_VALUE)
    @ApiOperation(value = "短信验证码的方式登录")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "clientId", value = "固定传 app", required = true, dataType = "String"),
            @ApiImplicitParam(name = "clientSecret", value = "固定传 app", required = true, dataType = "String"),
            @ApiImplicitParam(name = "phone", value = "手机号", required = true, dataType = "String"),
            @ApiImplicitParam(name = "smsCode", value = "验证码", required = true, dataType = "String"),
            @ApiImplicitParam(name = "isSurportRegister", value = "是否在登录时注册", dataType = "Boolean"),
            @ApiImplicitParam(name = "loginUserType", value = "登录用户类型 STUDENT TEACHER(官网登录)", dataType = "String")
    })
    public Object smsLogin() {
        return succeed();
    }

    @GetMapping("/queryUserInfo")
    public SysUser queryUserInfo() {
        AuthUser authUser = SecurityUtils.getUser();
        if (authUser != null) {
            return userService.get(authUser.getUserId());
        }
        return null;
    }

    @ApiOperation(value = "获取用户信息")
    @GetMapping("/api/queryUserInfo")
    public Object apiQueryUserInfo() {
        AuthUser authUser = SecurityUtils.getUser();
        if (authUser != null) {
            return succeed(userService.queryUserInfo(authUser.getUserId()));
        }
        return failed("获取用户信息失败");
    }

    @PostMapping(value = "/usernameLogin", consumes = MediaType.APPLICATION_JSON_VALUE)
    @ApiOperation(value = "手机号密码方式登录")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "clientId", value = "固定传 app", required = true, dataType = "String"),
            @ApiImplicitParam(name = "clientSecret", value = "固定传 app", required = true, dataType = "String"),
            @ApiImplicitParam(name = "username", value = "手机号", required = true, dataType = "String"),
            @ApiImplicitParam(name = "password", value = "密码", required = true, dataType = "String"),
            @ApiImplicitParam(name = "loginUserType", value = "登录用户类型 STUDENT TEACHER(官网登录)", dataType = "String")
    })
    public Object usernameLogin() {
        return succeed();
    }

    @PostMapping(value = "/refreshToken", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    @ApiOperation(value = "刷新token")
    public HttpResponseResult refreshToken(String refreshToken, String clientId, String clientSecret) throws IOException {
        String url = "http://auth-server/oauth/token";

        String base64ClientCredentials = Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes());

        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "Basic " + base64ClientCredentials);
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>();
        params.add("refresh_token", refreshToken);
        params.add("grant_type", "refresh_token");
        params.add("client_id", clientId);
        params.add("client_secret", clientSecret);

        HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<MultiValueMap<String, String>>(params, headers);

        ResponseEntity<String> resp = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);

        return succeed(objectMapper.readValue(resp.getBody(), Map.class));
    }

    @GetMapping(value = "exit")
    @ApiOperation(value = "退出登录")
    public HttpResponseResult<String> logout(HttpServletRequest request) {
        String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (StringUtils.isBlank(authHeader)) {
            return failed("退出失败，token 为空");
        }

        String tokenValue = authHeader.toLowerCase().replace(OAuth2AccessToken.BEARER_TYPE.toLowerCase(), StringUtils.EMPTY).trim();

        tokenService.revokeToken(tokenValue);

        return succeed("退出成功");
    }

    @PostMapping(value = "exit/{clientId}/{phone}", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    @ApiOperation(value = "指定用户退出登录")
    public HttpResponseResult<String> exitByPhone(@PathVariable("clientId") String clientId, @PathVariable("phone") String phone) {

        return tokenService.revokeToken(clientId, phone) ? succeed("退出成功") : failed();
    }

    @GetMapping(value = "/checkToken", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    @ApiOperation(value = "检查token")
    public HttpResponseResult<Object> checkToken(HttpServletRequest request) throws IOException {

        String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (StringUtils.isBlank(authHeader)) {
            return failed(HttpStatus.FORBIDDEN, "token 为空");
        }

        String tokenValue = authHeader.toLowerCase().replace(OAuth2AccessToken.BEARER_TYPE.toLowerCase(), StringUtils.EMPTY).trim();

        OAuth2AccessToken accessToken = resourceServerTokenServices.readAccessToken(tokenValue);
        if (accessToken == null) {
            return failed(HttpStatus.FORBIDDEN, "token 无效");
        }

        if (accessToken.isExpired()) {
            return failed(HttpStatus.FORBIDDEN, "token 已过期");
        }
        return succeed();
    }


    @GetMapping("/getQRLoginCode")
    @ApiOperation(value = "获取二维码登录code(不需要鉴权)")
    public HttpResponseResult<QRLoginDto> getQRLoginCode(
            @ApiParam(value = "clientId", required = true) @RequestParam("clientId") String clientId,
            @ApiParam(value = "clientSecret", required = true) @RequestParam("clientSecret") String clientSecret,
            @ApiParam(value = "code") String code) {
        if(StringUtils.isNotEmpty(code)){
            redissonClient.getBucket(CacheNameEnum.QR_LOGIN_CODE.getRedisKey(code)).delete();
        }

        QRLoginDto qrLogin = new QRLoginDto();

        UUID uuid = UUID.randomUUID();
        qrLogin.setCode(uuid.toString());

        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.MINUTE, 5);
        qrLogin.setExpireTime(instance.getTime());
        qrLogin.setClientId(clientId);
        qrLogin.setClientSecret(clientSecret);
        qrLogin.setCodeStatus("no_scan");

        redissonClient.getBucket(CacheNameEnum.QR_LOGIN_CODE.getRedisKey(uuid.toString()))
                .set(qrLogin, 5, TimeUnit.MINUTES);

        return HttpResponseResult.succeed(qrLogin);
    }


    @GetMapping("/pollingQRLoginCode")
    @ApiOperation(value = "前端轮询登录(不需要鉴权)")
    public HttpResponseResult<QRLoginDto> pollingQRLoginCode(@ApiParam(value = "二维码登录code", required = true) @RequestParam("code") String code) {
        if (StringUtil.isEmpty(code)) {
            return HttpResponseResult.failed("登录失败");
        }
        Object data = redissonClient.getBucket(CacheNameEnum.QR_LOGIN_CODE.getRedisKey(code))
                .get();
        if (null != data) {
            QRLoginDto dto = (QRLoginDto) data;
            return HttpResponseResult.succeed(dto);
        } else {
            return HttpResponseResult.succeed(null, "二维码已过期");
        }
    }

    @GetMapping(value = "/qrLogin")
    @ApiOperation(value = "二维码登录-扫码")
    public HttpResponseResult<QRLoginDto> qrLogin(
            @ApiParam(value = "二维码登录code", required = true) @RequestParam("code") String code
    ) throws IOException {
        AuthUser authUser = SecurityUtils.getUser();
        if (authUser == null) {
            return HttpResponseResult.failed("请先登录");
        }
        SysUser sysUser = userService.get(authUser.getUserId());

        Object data = redissonClient.getBucket(CacheNameEnum.QR_LOGIN_CODE.getRedisKey(code))
                .get();
        if (null == data) {
            return HttpResponseResult.failed("二维码已过期");
        }
        QRLoginDto dto = (QRLoginDto) data;
        dto.setCodeStatus("scanned");
        dto.setPhone(sysUser.getPhone());
        dto.setUserType(authUser.getClientId().toUpperCase());
        redissonClient.getBucket(CacheNameEnum.QR_LOGIN_CODE.getRedisKey(code)).set(dto, 5, TimeUnit.MINUTES);
        return succeed(dto);
    }

    @GetMapping(value = "/doQrLogin")
    @ApiOperation(value = "二维码登录-确认登录")
    public HttpResponseResult<QRLoginDto> doQrLogin(
            @ApiParam(value = "二维码登录code", required = true) @RequestParam("code") String code
    ) throws IOException {
        AuthUser authUser = SecurityUtils.getUser();
        if (authUser == null) {
            return HttpResponseResult.failed("请先登录");
        }
        SysUser sysUser = userService.get(authUser.getUserId());

        Object data = redissonClient.getBucket(CacheNameEnum.QR_LOGIN_CODE.getRedisKey(code))
                .get();
        if (null == data) {
            return HttpResponseResult.failed("二维码已过期");
        }
        QRLoginDto dto = (QRLoginDto) data;

        String url = "http://auth-server/smsLogin";

        String base64ClientCredentials = Base64.getEncoder().encodeToString((dto.getClientId() + ":" + dto.getClientSecret()).getBytes());

        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "Basic " + base64ClientCredentials);
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);

        url += "?clientId=" + dto.getClientId();
        url += "&clientSecret=" + dto.getClientSecret();
        url += "&phone=" + sysUser.getPhone();
        url += "&qrCode=" + code;
        url += "&loginUserType=" + authUser.getClientId().toUpperCase();

        HttpEntity<LinkedMultiValueMap<String, String>> entity = new HttpEntity<>(headers);

        ResponseEntity<String> resp = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);

        Map map = objectMapper.readValue(resp.getBody(), Map.class);
        Boolean status = (Boolean) map.get("status");
        if (status) {
            dto.setCodeStatus("succeed");
            Object obj = JSONObject.parseObject(JSONObject.toJSONString(map.get("data"))).get("authentication");
            dto.setAuthentication(obj);
        } else {
            dto.setCodeStatus("filed");
        }
        redissonClient.getBucket(CacheNameEnum.QR_LOGIN_CODE.getRedisKey(code)).set(dto, 5, TimeUnit.MINUTES);
        return HttpResponseResult.succeed(dto);
    }

    @GetMapping("/client")
    @ApiOperation(value = "获取登录用户client")
    public HttpResponseResult<String> client() throws IOException {
        AuthUser authUser = SecurityUtils.getUser();
        if (authUser == null) {
            return HttpResponseResult.failed("请先登录");
        }
        return succeed(authUser.getClientId());
    }
}
