package easydo.technology.system.security.rest;

import com.alibaba.fastjson.JSONObject;
import easydo.technology.core.processors.UserProcessor;
import easydo.technology.core.wx.EasydoWx;
import easydo.technology.system.security.config.bean.SecurityProperties;
import easydo.technology.system.security.security.TokenProvider;
import easydo.technology.system.security.service.OnlineUserService;
import easydo.technology.system.security.service.dto.JwtUserDto;
import easydo.technology.utils.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import zmo.technology.lite.core.ZeroController;
import zmo.technology.lite.core.ZeroException;
import zmo.technology.lite.core.ZeroRequest;
import zmo.technology.lite.core.ZeroRequestGuider;
import zmo.technology.lite.encrypt.ZeroEncrypt;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Controller
@RequestMapping(WxAuthController.WX_PREFIX)
public class WxAuthController extends ZeroController {

    public static final String WX_PREFIX = "wx";

    public WxAuthController() {
        super(WX_PREFIX);
    }

    @Autowired
    private DataSource dataSource;
    @Resource
    RedisUtils redisUtils;

    @Override
    protected String handlerNameConstructor(ZeroRequestGuider guider) throws Exception {
        if (guider.getPaths().size() < 1)
            throw new ZeroException(404, "no handlers");
        return guider.getPaths().get(0);
    }

    @RequestMapping(GENERAL_HANDLER_PATH)
    public void general(HttpServletRequest request, HttpServletResponse response) throws Exception {
        proxyHandler(dataSource, request, response);
    }

    @Autowired
    private SecurityProperties properties;

    @Autowired
    private OnlineUserService onlineUserService;

    @Autowired
    private TokenProvider tokenProvider;

    @Autowired
    private AuthenticationManagerBuilder authenticationManagerBuilder;

    public static class WxAuth {

        private String jscode;
        private String username;
        private String password;

        public WxAuth(String authString) {
            super();
            StringBuilder builder = new StringBuilder();
            for (int i = authString.length() - 9; i >= 0; i--)
                builder.append(authString.substring(i, i + 1));
            builder.append(authString.substring(authString.length() - 8));

            String decodeString = ZeroEncrypt.Base64DecodeToString(builder.toString());
            String head = ZeroEncrypt.Base64DecodeToString(decodeString.substring(0, 8));
            int jscodeLen = Integer.parseInt(head.substring(0, 2));
            int usernameLen = Integer.parseInt(head.substring(2, 4));
            int passwordLen = Integer.parseInt(head.substring(4, 6));

            jscode = decodeString.substring(8, 8 + jscodeLen);
            username = decodeString.substring(8 + jscodeLen, 8 + jscodeLen + usernameLen);
            password = decodeString.substring(8 + jscodeLen + usernameLen, 8 + jscodeLen + usernameLen + passwordLen);
        }

        public WxAuth(String jscode, String username, String password) {
            super();
            this.jscode = jscode;
            this.username = username;
            this.password = password;
        }

        public String encodeAuthString() throws Exception {
            if (jscode.length() > 99
                    || jscode.length() == 0
                    || username.length() > 99
                    || username.length() == 0
                    || password.length() > 99
                    || password.length() == 0)
                throw new ZeroException("invalid data length");

            String head = String.format("%02d", jscode.length()) +
                    String.format("%02d", username.length()) +
                    String.format("%02d", password.length());
            String content = jscode + username + password;
            String authString = ZeroEncrypt.Base64Encode(ZeroEncrypt.Base64Encode(head) + content);
            StringBuilder builder = new StringBuilder();
            for (int i = authString.length() - 9; i >= 0; i--)
                builder.append(authString.substring(i, i + 1));
            builder.append(authString.substring(authString.length() - 8));
            return builder.toString();
        }

        public String getJscode() {
            return jscode;
        }

        public void setJscode(String jscode) {
            this.jscode = jscode;
        }

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

        public String getPassword() {
            return password;
        }

        public void setPassword(String password) {
            this.password = password;
        }
    }

    private List<?> wxAutoLogin(ZeroRequestGuider guider, String openid) throws Exception {
        UserProcessor processor = UserProcessor.build(guider);
        JSONObject features = processor.fetchWxFeaturesByOpenID(openid);
        if (features == null)
            throw new ZeroException(11091, "wx openid not found");

        WxAuth auth = new WxAuth(features.getString("auth"));
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                processor.fetchUsernameByUserID(features.getLongValue("userID")), auth.password);
        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);

        String token = tokenProvider.createToken(authentication);
        JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal();
        onlineUserService.save(jwtUserDto, token, guider.getRequest());
        Map<String, Object> authInfo = new HashMap<String, Object>(2) {
            {
                put("token", properties.getTokenStartWith() + token);
                put("user", jwtUserDto);
            }
        };

        List<Object> datas = new ArrayList<>();
        datas.add(authInfo);
        return datas;
    }

    private static final String EXPANDS_AUTH = "auth";
    private static final String EXPANDS_JSCODE = "jscode";

    public void wxauto(ZeroRequestGuider guider) throws Exception {
        ZeroRequest<?> request = ZeroRequest.build(guider.getRawData());
        WxAuth auth = new WxAuth(request.getExpands().getString(EXPANDS_AUTH));

        JSONObject jsonObject = EasydoWx.exchangeOpenID(auth.jscode);
        UserProcessor processor = UserProcessor.build(guider);
        long userID = processor.fetchUserIDByUsername(auth.username);

        if (processor.fetchUserWxFeaturesCount(userID) > 0)
            throw new ZeroException("user `" + auth.username + "` already binding");

        JSONObject features = new JSONObject();
        features.put("auth",
                new WxAuth(jsonObject.getString(EasydoWx.OPEN_ID), "<placeholder>", auth.password).encodeAuthString());
        processor.addUserWxOpenID(userID, jsonObject.getString(EasydoWx.OPEN_ID), features);

        guider.write(success(wxAutoLogin(guider, jsonObject.getString(EasydoWx.OPEN_ID))));
    }

    public void wxauth(ZeroRequestGuider guider) throws Exception {
        ZeroRequest<?> request = ZeroRequest.build(guider.getRawData());
        String jscode = request.getExpands().getString(EXPANDS_JSCODE);
        JSONObject jsonObject = EasydoWx.exchangeOpenID(jscode);
        guider.write(success(wxAutoLogin(guider, jsonObject.getString(EasydoWx.OPEN_ID))));
    }

}
