package com.xhpc.evcs.notification;

import cn.hutool.core.date.DateUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.xhpc.evcs.domain.AuthSecretToken;
import com.xhpc.evcs.dto.CommonRequest;
import com.xhpc.evcs.dto.DTOJsonHelper;
import com.xhpc.evcs.dto.TokenRequest;
import com.xhpc.evcs.dto.TokenResponse;
import com.xhpc.evcs.http.ForbiddenException;
import com.xhpc.evcs.http.ServerInternalException;
import com.xhpc.evcs.jpa.AuthSecretTokenRepository;
import com.xhpc.evcs.utils.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.logging.HttpLoggingInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.transaction.annotation.Transactional;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import static com.xhpc.evcs.config.EvcsFilter.encryptReqOut;

@Slf4j
public class CoreDispatcher {

    @Value("${xhpc.evcs.oklog2:false}")
    private boolean oklog2;
    @Autowired
    private AuthSecretTokenRepository authSecretTokenRepository;
    public static final okhttp3.MediaType JSON = okhttp3.MediaType.parse("application/json; charset=utf-8");

    private final Logger logger = LoggerFactory.getLogger(CoreDispatcher.class);

    @Transactional
    public String ok(Object object, String url, AuthSecretToken authSecretTokenOut) {

        return ok(object, url, authSecretTokenOut, null);
    }

    @Transactional
    public String okYu(Object object, String url, AuthSecretToken authSecretTokenOut,String stationOperatorId) {

        return ok(object, url, authSecretTokenOut, stationOperatorId);
    }

    @Transactional
    public String ok(Object object, String url, AuthSecretToken authSecretTokenOut, String stationOperatorId) {
        if (authSecretTokenOut == null) {
            log.error("authSecretTokenOut is null");
            throw new RuntimeException();
        }
        okhttp3.RequestBody body = null;
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        if (oklog2) {
            HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
            logging.setLevel(HttpLoggingInterceptor.Level.BODY);
            builder.addInterceptor(logging);
        }
        builder.connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS);
        Calendar cal = Calendar.getInstance();
        String bearerToken = null;
        try {
            if (object.getClass().getSimpleName().equals("CommonRequest")) {
                CommonRequest commonRequest = (CommonRequest) object;
                if (stationOperatorId != null) {
                    commonRequest.setOperatorId(stationOperatorId);
                } else {
                    commonRequest.setOperatorId(authSecretTokenOut.getOperatorId());
                }
                Date tokenExpiry = authSecretTokenOut.getTokenExpiry();
                String originalData = commonRequest.getData();
                String tData;
                if (tokenExpiry == null || tokenExpiry.before(cal.getTime())) {
                    log.debug("query notify token opid3rdpt [{}]", authSecretTokenOut.getOperatorId3irdpty());
                    TokenRequest tokenRequest = new TokenRequest();
                    tokenRequest.setOperatorId(authSecretTokenOut.getOperatorId());
                    tokenRequest.setOperatorSecret(authSecretTokenOut.getOperatorSecret());
                    commonRequest.setData(JSONUtil.toJSONString(tokenRequest));
                    tData = JSONUtil.toJSONString(commonRequest);
                    if (authSecretTokenOut.isEncrypt()) {
                        tData = encryptReqOut(authSecretTokenOut.getDataSecret(), authSecretTokenOut.getDataSecretIV(),
                                authSecretTokenOut.getSigSecret(), commonRequest);
                    }
                    Request request = new Request.Builder()
                            .url(authSecretTokenOut.getUrlPrefix() + "/query_token")
                            .post(okhttp3.RequestBody.create(JSON, tData))
                            .build();
                    OkHttpClient client = builder.build();
                    String tokenResponse = fwdToInfra(request, client);
                    TokenResponse tr = DTOJsonHelper.parseResponseData(tokenResponse, TokenResponse.class, authSecretTokenOut);
                    if (tr != null) {
                        String token = tr.getAccessToken();
                        Integer tokenAvailableTime = tr.getTokenAvailableTime();
                        authSecretTokenOut.setToken(token);
                        authSecretTokenOut.setTokenExpiry(DateUtil.date(Calendar.getInstance().getTimeInMillis() + (tokenAvailableTime) * 1000L - 300));
                        authSecretTokenRepository.save(authSecretTokenOut);
                    }
                }
                bearerToken = authSecretTokenOut.getToken();
                commonRequest.setData(originalData);
                tData = JSONUtil.toJSONString(commonRequest);
               // System.out.println("=========加密前data=============="+tData);
                //log.debug("to [{}] {} out plain data:\n{}", authSecretTokenOut.getOperatorId3irdpty(), url, tData);
                if (authSecretTokenOut.isEncrypt()) {
                    tData = encryptReqOut(authSecretTokenOut.getDataSecret(), authSecretTokenOut.getDataSecretIV(),
                            authSecretTokenOut.getSigSecret(), commonRequest);
//                    log.debug("notify out encrypt data:\n{}", tData);
                  //  System.out.println("=========加密data=============="+tData);
                }
                body = okhttp3.RequestBody.create(JSON, tData);
//                logger.info("==============平台推送值================="+tData);
            }

        } catch (JsonProcessingException | BadPaddingException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException | InvalidKeyException e) {
            String msg = e.getMessage();
            log.error(msg);
            throw new ServerInternalException(msg);
        }

        final Request.Builder req = new Request.Builder()
                .url(authSecretTokenOut.getUrlPrefix() + url)
                .header("Authorization", "Bearer " + bearerToken);
        Request request;
        assert body != null;
//        if (body==null) request = req.get().build();
//        else
       // logger.info("==============body================="+body.toString());
        request = req.post(body).build();
        OkHttpClient client = builder.build();
        return fwdToInfra(request, client);
    }

    private String fwdToInfra(Request request, OkHttpClient client) {

        String responseBody = null;
        try {
            Response response = client.newCall(request).execute();
            if (response.isSuccessful()) {
                ResponseBody body = response.body();
                responseBody = body == null ? null : body.string();
            } else {
                int code = response.code();
                if (code == 401 || code == 403) {
                    throw new ForbiddenException("Remote system returned " + code);
                }
            }
        } catch (IOException e) {
            String msg = e.getMessage();
            log.error(msg);
            throw new ServerInternalException(msg);
        }
        return responseBody;
    }

    private AuthSecretToken getAuthSecretTokenOut(String operatorId3irdpty, String operatorID) {

        return authSecretTokenRepository.findByOperatorId3irdptyAndOperatorIdAndSecretTokenType(operatorId3irdpty, operatorID,
                AuthSecretToken.SECRET_TOKEN_TYPE_OUT).orElse(null);
    }

    public String ceshiOk(Object object, String url, AuthSecretToken authSecretTokenOut) {

        return ceshiOk(object, url, authSecretTokenOut, null);
    }

    @Transactional
    public String ceshiOk(Object object, String url, AuthSecretToken authSecretTokenOut, String stationOperatorId) {
        if (authSecretTokenOut == null) {
            log.error("authSecretTokenOut is null");
            throw new RuntimeException();
        }
        okhttp3.RequestBody body = null;
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        if (oklog2) {
            HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
            logging.setLevel(HttpLoggingInterceptor.Level.BODY);
            builder.addInterceptor(logging);
        }
        builder.connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS);
        Calendar cal = Calendar.getInstance();
        String bearerToken = null;
        try {
            if (object.getClass().getSimpleName().equals("CommonRequest")) {
                CommonRequest commonRequest = (CommonRequest) object;
                if (stationOperatorId != null) {
                    commonRequest.setOperatorId(stationOperatorId);
                } else {
                    commonRequest.setOperatorId(authSecretTokenOut.getOperatorId());
                }
                Date tokenExpiry = authSecretTokenOut.getTokenExpiry();
                String originalData = commonRequest.getData();
                String tData;
                if (tokenExpiry == null || tokenExpiry.before(cal.getTime())) {
                    log.debug("query notify token opid3rdpt [{}]", authSecretTokenOut.getOperatorId3irdpty());
                    TokenRequest tokenRequest = new TokenRequest();
                    tokenRequest.setOperatorId(authSecretTokenOut.getOperatorId());
                    tokenRequest.setOperatorSecret(authSecretTokenOut.getOperatorSecret());
                    commonRequest.setData(JSONUtil.toJSONString(tokenRequest));
                    tData = JSONUtil.toJSONString(commonRequest);
                    if (authSecretTokenOut.isEncrypt()) {
                        tData = encryptReqOut(authSecretTokenOut.getDataSecret(), authSecretTokenOut.getDataSecretIV(),
                                authSecretTokenOut.getSigSecret(), commonRequest);
                    }
                    Request request = new Request.Builder()
                            .url(authSecretTokenOut.getUrlPrefix() + "/query_token")
                            .post(okhttp3.RequestBody.create(JSON, tData))
                            .build();
                    OkHttpClient client = builder.build();
                    String tokenResponse = fwdToInfra(request, client);
                    TokenResponse tr = DTOJsonHelper.parseResponseData(tokenResponse, TokenResponse.class, authSecretTokenOut);
                    if (tr != null) {
                        String token = tr.getAccessToken();
                        Integer tokenAvailableTime = tr.getTokenAvailableTime();
                        authSecretTokenOut.setToken(token);
                        authSecretTokenOut.setTokenExpiry(DateUtil.date(Calendar.getInstance().getTimeInMillis() + (tokenAvailableTime) * 1000L - 300));
                        authSecretTokenRepository.save(authSecretTokenOut);
                    }
                }
                bearerToken = authSecretTokenOut.getToken();
                commonRequest.setData(originalData);
                tData = JSONUtil.toJSONString(commonRequest);
               // log.debug("to [{}] {} out plain data:\n{}", authSecretTokenOut.getOperatorId3irdpty(), url, tData);
                if (authSecretTokenOut.isEncrypt()) {
                    tData = encryptReqOut(authSecretTokenOut.getDataSecret(), authSecretTokenOut.getDataSecretIV(),
                            authSecretTokenOut.getSigSecret(), commonRequest);
//                    log.debug("notify out encrypt data:\n{}", tData);
                }
                body = okhttp3.RequestBody.create(JSON, tData);
//                logger.info("==============平台推送值================="+tData);
            }

        } catch (JsonProcessingException | BadPaddingException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException | InvalidKeyException e) {
            String msg = e.getMessage();
            log.error(msg);
            throw new ServerInternalException(msg);
        }

        final Request.Builder req = new Request.Builder()
                .url(authSecretTokenOut.getUrlPrefix() + url)
                .header("Authorization", "Bearer " + bearerToken);
        Request request;
        assert body != null;
//        if (body==null) request = req.get().build();
//        else
       // logger.info("==============body================="+body.toString());
        request = req.post(body).build();
        OkHttpClient client = builder.build();
        return fwdToInfra(request, client);
    }



}
