package com.njzscloud.common.core.http.resolver;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.njzscloud.common.core.http.annotation.MultiBodyParam;
import com.njzscloud.common.core.http.constant.HttpMethod;
import com.njzscloud.common.core.http.constant.Mime;
import com.njzscloud.common.core.http.processor.MultiBodyParamProcessor;
import com.njzscloud.common.core.tuple.Tuple2;
import com.njzscloud.common.core.tuple.Tuple3;

import java.io.ByteArrayOutputStream;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

public class MultiBodyParamResolver extends ParamResolver<MultiBodyParam, Tuple2<String, byte[]>> {
    private static final byte[] DASHDASH = "--".getBytes(StandardCharsets.UTF_8);
    private static final byte[] CRLF = StrUtil.CRLF.getBytes(StandardCharsets.UTF_8);
    Map<String, Tuple3<String, String, byte[]>> result = new HashMap<>();

    public MultiBodyParamResolver() {
        super(MultiBodyParam.class);
    }

    @Override
    public Tuple2<String, byte[]> resolve(HttpMethod httpMethod, String urlTpl) {
        if (result.isEmpty()) return null;

        String boundary = RandomUtil.randomString(16);
        String contentType = Mime.MULTIPART_FORM + "; boundary=" + boundary;
        byte[] bytes = buildContent(boundary.getBytes(StandardCharsets.UTF_8));
        return Tuple2.create(contentType, bytes);
    }

    private byte[] buildContent(byte[] boundary) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

        result.forEach((k, v) -> {
            String filename = URLUtil.encode(v.get_0());
            String contentType = v.get_1();
            byte[] bytes = v.get_2();

            IoUtil.write(byteArrayOutputStream, false, DASHDASH);
            IoUtil.write(byteArrayOutputStream, false, boundary);
            IoUtil.write(byteArrayOutputStream, false, CRLF);

            byte[] contentDisposition;
            if (StrUtil.isNotBlank(filename)) {
                contentDisposition = StrUtil.format("Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"", k, filename).getBytes(StandardCharsets.UTF_8);
            } else {
                contentDisposition = StrUtil.format("Content-Disposition: form-data; name=\"{}\"", k).getBytes(StandardCharsets.UTF_8);
            }

            IoUtil.write(byteArrayOutputStream, false, contentDisposition);
            IoUtil.write(byteArrayOutputStream, false, CRLF);

            if (StrUtil.isNotBlank(contentType)) {
                IoUtil.write(byteArrayOutputStream, false, ("Content-Type: " + contentType).getBytes(StandardCharsets.UTF_8));
                IoUtil.write(byteArrayOutputStream, false, CRLF);
            }

            IoUtil.write(byteArrayOutputStream, false, CRLF);
            IoUtil.write(byteArrayOutputStream, false, bytes);
            IoUtil.write(byteArrayOutputStream, false, CRLF);

        });
        IoUtil.write(byteArrayOutputStream, false, DASHDASH);
        IoUtil.write(byteArrayOutputStream, false, boundary);
        IoUtil.write(byteArrayOutputStream, false, DASHDASH);

        return byteArrayOutputStream.toByteArray();
    }

    @Override
    @SuppressWarnings("unchecked")
    protected boolean resolveParameter(MultiBodyParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
        if (isKv(paramClazz)) resolveKv(paramValue);
        else if (isStr(paramClazz) || isNum(paramClazz) || isDt(paramClazz) || isBytes(paramClazz) || isIn(paramClazz) || isFile(paramClazz)) resolveFormable(anno, paramName, paramClazz, paramValue);
        else return false;
        return true;
    }

    @Override
    @SuppressWarnings("unchecked")
    protected boolean resolveField(boolean hasParameterAnno, MultiBodyParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
        if (anno != null) {
            Class<? extends MultiBodyParamProcessor> processorClazz = anno.processor();
            if (processorClazz != MultiBodyParamProcessor.class) {
                ReflectUtil.newInstance(processorClazz).process(anno, paramName, paramClazz, paramValue, result);
            } else if (!hasParameterAnno && isKv(paramClazz)) {
                resolveKv(paramValue);
            }
        }
        resolveFormable(anno, paramName, paramClazz, paramValue);
        return true;
    }

    protected void resolveFormable(MultiBodyParam anno, String paramName, Class<?> paramClazz, Object paramValue) {
        String key = paramName;
        byte[] val;
        String mime;
        String filename = null;

        String format = null;
        RoundingMode roundingMode = null;
        if (anno != null) {
            format = anno.format();
            roundingMode = anno.roundingMode();
            String value = anno.value();
            if (StrUtil.isNotBlank(value)) key = value;
        }

        if (isNum(paramClazz)) {
            mime = Mime.TXT;
            val = formatNum(format, roundingMode, paramValue).getBytes(StandardCharsets.UTF_8);
        } else if (isDt(paramClazz)) {
            mime = Mime.TXT;
            val = formatDt(format, paramValue).getBytes(StandardCharsets.UTF_8);
        } else if (isBytes(paramClazz)) {
            mime = Mime.BINARY;
            val = (byte[]) paramValue;
        } else if (isIn(paramClazz) || isFile(paramClazz)) {
            mime = Mime.BINARY;
            Tuple2<String, byte[]> fileInfo = toFileInfo(paramValue);
            filename = fileInfo.get_0();
            val = fileInfo.get_1();
        } else {
            mime = Mime.TXT;
            val = paramValue.toString().getBytes(StandardCharsets.UTF_8);
        }

        result.put(key, Tuple3.create(filename, mime, val));

    }

}
