package com.ensign.crm.module.crm.service;


import cn.hutool.core.codec.Base64;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ensign.crm.module.crm.exception.AllKingdeeException;
import com.ensign.crm.module.crm.model.FileStorage;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.InputStreamBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.MimeType;
import org.springframework.util.StreamUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @Description: TODO
 * @Date: 2024/9/26 17:56
 * @Created: by ZZSLL
 */

@Service
@Slf4j
public class ProxyService {

    @Value("${ensign.kingdee.is-public}")
    private Boolean isPublic;

    @Value("${ensign.kingdee.test-public-end-point}")
    private String testPublicEndPoint;

    @Value("${ensign.kingdee.test-inner-end-point}")
    private String testInnerEndPoint;

    @Value("${ensign.kingdee.prod-public-end-point}")
    private String prodPublicEndPoint;

    @Value("${ensign.kingdee.prod-inner-end-point}")
    private String prodInnerEndPoint;

    @Value("${ensign.kingdee.env}")
    private String env;

    @Value("${ensign.kingdee.appToken.appId}")
    private String appId;

    @Value("${ensign.kingdee.appToken.appSecuret}")
    private String appSecuret;

    @Value("${ensign.kingdee.appToken.tenantid}")
    private String tenantid;

    @Value("${ensign.kingdee.appToken.accountId}")
    private String accountId;

    @Value("${ensign.kingdee.appToken.language}")
    private String language;

    @Value("${ensign.kingdee.accessToken.user}")
    private String user;

    @Value("${ensign.kingdee.accessToken.usertype}")
    private String usertype;

    private final String redisKingdeeKey = "kingdee:accessToken";

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private KingdeeFileService kingdeeFileService;


    public void doProxy(HttpServletRequest request, HttpServletResponse response) throws IOException, URISyntaxException {
        URI uri = new URI(request.getRequestURI());
        String path = uri.getPath();
        String query = request.getQueryString();
        if (path.contains("/unauth")) {
            path = path.replace("/unauth", "");
        }
        String target = initBasePath() + path.replace("/crm-api/proxy/do", "");
        String accessToken;
        try {
            accessToken = initAccessToken();

            log.info("Header: access_token: {}", accessToken);
        } catch (AllKingdeeException e) {
            log.error("获取accessToken时失败：{}", e.getMessage());
            log.info("================request kingdee end================");
            response.setStatus(HttpServletResponse.SC_OK);
            response.setContentType("application/json");
            response.setCharacterEncoding("UTF-8");
            Map<String, Object> body = new HashMap<>();
            body.put("status", false);
            body.put("message", e.getMessage());
            response.getWriter().println(JSONUtil.toJsonStr(body));
            response.getWriter().flush();
            return;
        }

        if (query != null && !query.isEmpty() && !query.equals("null")) {
            target = target + "?" + query;
        }
        URI newUri = new URI(target);

        log.info("================request kingdee start================");
        log.info("Request Parameters:");
        log.info("URI: {}", newUri);
        log.info("Method: {}", request.getMethod());

        // 执行代理查询
        String methodName = request.getMethod();
        HttpMethod httpMethod = HttpMethod.resolve(methodName);
        if (httpMethod == null) {
            return;
        }
        ClientHttpRequest delegate = new SimpleClientHttpRequestFactory().createRequest(newUri, httpMethod);
        Enumeration<String> headerNames = request.getHeaderNames();
        // 设置请求头
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            Enumeration<String> v = request.getHeaders(headerName);
            List<String> arr = new ArrayList<>();
            while (v.hasMoreElements()) {
                arr.add(v.nextElement());
            }
            log.info("Header: {}: {}", headerName, arr);
            delegate.getHeaders().addAll(headerName, arr);
            delegate.getHeaders().add("access_token", accessToken);
        }
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        int contentLength = request.getContentLength();
        byte[] buffer = new byte[4096];
        int bytesRead;
        int hasRead = 0;
        while ((bytesRead = request.getInputStream().read(buffer)) != -1) {
            byteArrayOutputStream.write(buffer, 0, bytesRead);
            hasRead += bytesRead;
            if (hasRead == contentLength) {
                break;
            }
        }
        byte[] bytes = byteArrayOutputStream.toByteArray();

        delegate.getBody().write(bytes);
        // 执行远程调用
        ClientHttpResponse clientHttpResponse = delegate.execute();
        log.info("Response Status: {}", clientHttpResponse.getStatusCode().value());
        clientHttpResponse.getHeaders().forEach((key, value) -> value.forEach(it -> {
            log.info("Response Header: {}: {}", key, it);
        }));

        response.setStatus(clientHttpResponse.getStatusCode().value());
        // 设置响应头
//        clientHttpResponse.getHeaders().forEach((key, value) -> value.forEach(it -> {
//            response.setHeader(key, it);
//        }));
        response.setHeader("Content-Encoding", "gzip");
        response.setHeader("Content-Type", "application/json;charset=UTF-8");
        log.info("================request kingdee end================");
        StreamUtils.copy(clientHttpResponse.getBody(), response.getOutputStream());
    }

    public String doProxyFile(HttpServletRequest request, MultipartFile file) throws IOException, URISyntaxException, AllKingdeeException {
        URI uri = new URI(request.getRequestURI());
        String path = uri.getPath();
        String target = initBasePath() + path.replace("/crm-api/proxy/file/do", "");
        String accessToken = initAccessToken();
        target = target + "?access_token=" + accessToken;
        log.info("proxy target: {}",target);
        URI newUri = new URI(target);
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(newUri);
        MultipartEntity builder = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE, "----WebKitFormBoundaryBHp47SjGMxpHX0X6--", Charset.forName("UTF-8"));
//        FileBody fileBody = new FileBody(transferToFile(file));
//        builder.addPart("file", fileBody);
        File transferToFile = transferToFile(file);
        builder.addPart("file", new InputStreamBody(new FileInputStream(transferToFile), file.getOriginalFilename()));
        httpPost.addHeader("Content-Type",  "multipart/form-data; boundary=----WebKitFormBoundaryBHp47SjGMxpHX0X6--");
        httpPost.setEntity(builder);
        HttpResponse kResponse = httpClient.execute(httpPost);
        Header[] headers = kResponse.getAllHeaders();
        for (Header header : headers) {
            log.info("response Header: {}: {}", header.getName(), header.getValue());
        }
        HttpEntity entity = kResponse.getEntity();
        int statusCode = kResponse.getStatusLine().getStatusCode();
        log.info("response status: {}", statusCode);
        String jsonStr = EntityUtils.toString(entity, "UTF-8").trim();

        log.info("receive json response: {}", jsonStr);
        JSONObject jsonObject = JSON.parseObject(jsonStr);

        Header[] requestHeader = httpPost.getAllHeaders();
        for (Header header : requestHeader) {
            log.info("request Header: {}: {}", header.getName(), header.getValue());
        }
        if (jsonObject.containsKey("url")) {
            return jsonObject.getString("url");
        }
        return null;
    }

    /**
     * 下载并保存图片文件
     *
     * @param realPath 文件路径
     * @return
     * @throws AllKingdeeException
     * @throws IOException
     */
    private FileStorage doDownloadImage(String realPath) throws AllKingdeeException, IOException {
        String url;
        if (realPath.startsWith("/")) {
            url = initBasePath() + "/ierp/attachment/downloadImage" + realPath;
        } else {
            url = initBasePath() + "/ierp/attachment/downloadImage/" + realPath;
        }

        String downloadUrl = url + "?v=1.0&access_token=" + initAccessToken();
        byte[] bytes = kingdeeFileService.downloadFile(downloadUrl);
        FileStorage fileStorage = new FileStorage();
        String filename = getFileNameFromPath(realPath);
        fileStorage.setMediaType(getFileMediaType(filename));
        fileStorage.setFileName(filename);
        fileStorage.setFileSize((long) bytes.length);
        fileStorage.setFileContent(transformToBase64(bytes));

        kingdeeFileService.saveImage(realPath, fileStorage);
        return fileStorage;
    }


    /**
     * 从苍穹下载文件
     *
     * @param url       文件下载地址
     * @param fileName
     * @return
     * @throws AllKingdeeException
     * @throws IOException
     */
    private FileStorage doDownloadFile(String url, String fileName) throws AllKingdeeException, IOException, URISyntaxException {

        URI uri = new URI(url);

        String downloadUrl = url + "&access_token=" + initAccessToken();
        byte[] bytes = kingdeeFileService.downloadFile(downloadUrl);
        FileStorage fileStorage = new FileStorage();
        String query = uri.getQuery();

        String pathValue = getQueryParamValue(query, "path");
        fileStorage.setMediaType("application/octet-stream;charset=utf-8");
        fileStorage.setFileName(fileName);
        fileStorage.setFileSize((long) bytes.length);
        fileStorage.setFileContent(transformToBase64(bytes));

        kingdeeFileService.saveFile(pathValue, fileStorage);
        return fileStorage;
    }

    // 获取指定参数名的值
    private static String getQueryParamValue(String query, String param) {
        if (query != null) {
            String[] pairs = query.split("&");
            for (String pair : pairs) {
                String[] keyValue = pair.split("=");
                if (keyValue.length > 1 && keyValue[0].equals(param)) {
                    return keyValue[1];
                }
            }
        }
        return null; // 如果没有找到，返回 null
    }

    /**
     * 提取文件名称
     * @param realPath 文件路径
     * @return 文件名称
     */
    private String getFileNameFromPath(String realPath) {
        Path path = Paths.get(realPath);
        return path.getFileName().toString();
    }

    private String getFileMediaType(String filename) {
        log.info("filename: {}", filename);
        Optional<MediaType> mediaType = MediaTypeFactory.getMediaType(filename);
        String type = mediaType.map(MimeType::toString).orElse("");
        log.info("mediaType: {}", type);
        return type;
    }


    private String transformToBase64(byte[] file) {
        return Base64.encode(file);
    }

    public File transferToFile(MultipartFile multipartFile) throws IOException {
        String originalFilename = multipartFile.getOriginalFilename();
        assert originalFilename != null;
        String[] filename = originalFilename.split("\\.");
        File file = File.createTempFile(filename[0], "." + filename[1]);
        multipartFile.transferTo(file);
        file.deleteOnExit();
        return file;
    }

    public JSONObject doRequestPost(String endpoint, String methodName, Map<String, Object> body, Map<String, Object> header) throws AllKingdeeException, IOException, URISyntaxException {
        log.info("================request kingdee start================");

        HttpClient httpClient = HttpClients.createDefault();
        HttpMethod httpMethod = HttpMethod.resolve(methodName);

        URI newUri = new URI(initBasePath() + endpoint);

        if (httpMethod == null) {
            throw new AllKingdeeException("unknown request methodName: " + methodName);
        }

        HttpPost request = new HttpPost(newUri);
        if (body != null) {
            StringEntity entity = new StringEntity(JSON.toJSONString(body), "UTF-8");
            entity.setContentType("application/json;charset=utf-8");
            request.setEntity(entity);
        }

        if (header != null) {
            for (Map.Entry<String, Object> entry : header.entrySet()) {
                request.addHeader(entry.getKey(), entry.getValue().toString());
            }
        }
        request.addHeader("access_token", initAccessToken());
        log.info("Requesting: {}", request);
        Header[] headers = request.getAllHeaders();
        for (Header hd : headers) {
            log.info("header: {}", hd);
        }

        HttpResponse response = httpClient.execute(request);


        if (response.getStatusLine().getStatusCode() == 200) {
            HttpEntity entity = response.getEntity();
            String jsonResponse = EntityUtils.toString(entity);
            log.info("Received response: {}", jsonResponse);
            log.info("================request kingdee end================");
            return JSON.parseObject(jsonResponse);
        } else {
            throw new AllKingdeeException("request error: " + response.getStatusLine().getReasonPhrase());
        }
    }


    public String initAccessToken() throws IOException, AllKingdeeException {
        String accessToken = redisTemplate.opsForValue().get(redisKingdeeKey);
        if (accessToken != null && !accessToken.isEmpty()) {
            return accessToken;
        }

        final String END_POINT = initBasePath() + "/ierp/api/getAppToken.do";
        HttpClient client = new DefaultHttpClient();

        HttpPost post = new HttpPost(END_POINT);

        Map<String, String> body = new HashMap<>();

        body.put("appId", appId);
        body.put("appSecuret", appSecuret);
        body.put("tenantid", tenantid);
        body.put("accountId", accountId);
        body.put("language", language);

        HttpEntity reqEntity = new StringEntity(JSONUtil.toJsonStr(body), "utf-8");

        post.setEntity(reqEntity);
        post.setConfig(initRequestConfig());
        HttpResponse response = client.execute(post);
        if (response.getStatusLine().getStatusCode() == 200) {

            HttpEntity resEntity = response.getEntity();
            String responseData = EntityUtils.toString(resEntity, "utf-8");
            com.alibaba.fastjson.JSONObject jsonObject = JSON.parseObject(responseData);
            Boolean status = (Boolean) jsonObject.get("status");
            String state = (String) jsonObject.get("state");
            if (status && "success".equals(state)) {
                JSONObject data = (JSONObject) jsonObject.get("data");
                String appToken = (String) data.get("app_token");
                if (appToken != null && !appToken.isEmpty()) {
                    return doGetAccessToken(appToken);
                }
            }
        }
        throw new AllKingdeeException("获取AppToken失败");
    }

    private String doGetAccessToken(String appToken) throws IOException, AllKingdeeException {

        HttpClient client = new DefaultHttpClient();

        HttpPost post = new HttpPost(initBasePath() + "/ierp/api/login.do");

        Map<String, String> body = new HashMap<>();

        body.put("user", user);
        body.put("apptoken", appToken);
        body.put("tenantid", tenantid);
        body.put("accountId", accountId);
        body.put("language", language);
        body.put("usertype", usertype);

        HttpEntity reqEntity = new StringEntity(JSONUtil.toJsonStr(body), "utf-8");

        post.setEntity(reqEntity);
        post.setConfig(initRequestConfig());
        HttpResponse response = client.execute(post);

        if (response.getStatusLine().getStatusCode() == 200) {

            HttpEntity resEntity = response.getEntity();
            String responseData = EntityUtils.toString(resEntity, "utf-8");
            JSONObject jsonObject = JSON.parseObject(responseData);
            Boolean status = (Boolean) jsonObject.get("status");
            String state = (String) jsonObject.get("state");

            if (status && "success".equals(state)) {
                JSONObject data = (JSONObject) jsonObject.get("data");
                String accessToken = (String) data.get("access_token");
                if (accessToken != null && !accessToken.isEmpty()) {

                    Long expireTime = (Long) data.get("expire_time");
                    redisTemplate.opsForValue().set(redisKingdeeKey, accessToken, expireTime / 1000 - 3000, TimeUnit.MICROSECONDS);
                    return accessToken;
                }
            }
        }
        throw new AllKingdeeException("获取AccessToken失败");
    }

    public RequestConfig initRequestConfig() {
        return RequestConfig.custom()
                .setConnectTimeout(5000)//一、连接超时：connectionTimeout-->指的是连接一个url的连接等待时间
                .setSocketTimeout(5000)// 二、读取数据超时：SocketTimeout-->指的是连接上一个url，获取response的返回等待时间
                .setConnectionRequestTimeout(5000)
                .build();
    }

    public String initBasePath() {
        if ("test".equals(env)) {
            if (isPublic) {
                return testPublicEndPoint;
            } else {
                return testInnerEndPoint;
            }
        }

        if ("prod".equals(env)) {
            if (isPublic) {
                return prodPublicEndPoint;
            } else {
                return prodInnerEndPoint;
            }
        }

        return "";
    }

    public String doConvertFileUrl(String path) {
        return "/crm-api/proxy/file/read/do?path=" + path;
    }

    public void doGetImage(String path, HttpServletResponse response) throws AllKingdeeException, IOException {
        FileStorage image = kingdeeFileService.getImage(path);
        if (image == null) {
            image = doDownloadImage(path);
        }
        byte[] bytes = Base64.decode(image.getFileContent());

        try {
            response.setContentType(image.getMediaType());
            response.setContentLength(image.getFileSize().intValue());
            response.setHeader("Content-Disposition", "inline; filename=\"" + image.getFileName() + "\"");

            ServletOutputStream outputStream = response.getOutputStream();
            outputStream.write(bytes);
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * 从苍穹下载文件
     *
     * @param url       文件下载地址
     * @param file_name
     * @param response  传输文件流
     * @throws AllKingdeeException
     * @throws IOException
     */
    public void doGetFile(String url, String file_name, HttpServletResponse response) throws AllKingdeeException, IOException, URISyntaxException {
        FileStorage file = kingdeeFileService.getFile(url);
        if (file == null) {
            file = doDownloadFile(url, file_name);
        }
        byte[] bytes = Base64.decode(file.getFileContent());

        try {
            response.setContentType(file.getMediaType());
            response.setContentLength(file.getFileSize().intValue());
            response.setHeader("Content-Disposition", "inline; filename=\"" + file.getFileName() + "\"");

            ServletOutputStream outputStream = response.getOutputStream();
            outputStream.write(bytes);
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }
}
