package com.easyagents.document.core.mineru;

import com.alibaba.fastjson2.JSONObject;
import com.easyagents.core.util.StringUtil;
import com.easyagents.document.core.exception.DocumentParseException;
import com.easyagents.document.core.entity.ParseFile;
import com.easyagents.document.core.entity.ParseRequest;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;

import java.io.IOException;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * MinerU HTTP 客户端。
 *
 * @author Codex
 * @since 2026-04-16
 */
public class MineruClient {

    private static final MediaType DEFAULT_MEDIA_TYPE = MediaType.parse("application/octet-stream");

    private final String baseUrl;
    private final OkHttpClient okHttpClient;
    private final MineruMapper mineruMapper;

    /**
     * 创建客户端。
     *
     * @param properties MinerU 配置
     * @param mineruMapper DTO 映射器
     */
    public MineruClient(MineruProperties properties, MineruMapper mineruMapper) {
        this(
            properties,
            new OkHttpClient.Builder()
                .connectTimeout(properties.getConnectTimeoutMs(), TimeUnit.MILLISECONDS)
                .readTimeout(properties.getReadTimeoutMs(), TimeUnit.MILLISECONDS)
                .writeTimeout(properties.getWriteTimeoutMs(), TimeUnit.MILLISECONDS)
                .build(),
            mineruMapper
        );
    }

    /**
     * 创建客户端。
     *
     * @param properties MinerU 配置
     * @param okHttpClient HTTP 客户端
     * @param mineruMapper DTO 映射器
     */
    public MineruClient(MineruProperties properties, OkHttpClient okHttpClient, MineruMapper mineruMapper) {
        if (properties == null || !StringUtil.hasText(properties.getBaseUrl())) {
            throw new IllegalArgumentException("MinerU baseUrl must not be empty");
        }
        this.baseUrl = normalizeBaseUrl(properties.getBaseUrl());
        this.okHttpClient = okHttpClient;
        this.mineruMapper = mineruMapper;
    }

    /**
     * 调用同步解析接口。
     *
     * @param request 解析请求
     * @return 原始结果
     */
    public MineruResultPayload parse(ParseRequest request) {
        return mineruMapper.toResultPayload(executeJsonMultipart("/file_parse", request, buildSyncFormFields(request)));
    }

    /**
     * 提交异步解析任务。
     *
     * @param request 解析请求
     * @return 原始任务状态
     */
    public MineruTaskStatus submit(ParseRequest request) {
        return mineruMapper.toTaskStatus(executeJsonMultipart("/tasks", request, buildAsyncFormFields(request)));
    }

    /**
     * 查询任务状态。
     *
     * @param taskId 任务 ID
     * @return 原始任务状态
     */
    public MineruTaskStatus queryTask(String taskId) {
        return mineruMapper.toTaskStatus(executeJsonGet("/tasks/" + taskId));
    }

    /**
     * 下载异步结果 ZIP。
     *
     * @param taskId 任务 ID
     * @return ZIP 二进制
     */
    public byte[] queryResultZip(String taskId) {
        String path = "/tasks/" + taskId + "/result";
        Request request = new Request.Builder().url(baseUrl + path).get().build();
        try (Response response = okHttpClient.newCall(request).execute()) {
            ResponseBody body = response.body();
            byte[] responseBytes = body == null ? new byte[0] : body.bytes();
            if (!response.isSuccessful()) {
                throw buildHttpException(path, response.code(), responseBytes);
            }
            String contentType = response.header("Content-Type");
            if (contentType != null && contentType.contains("application/json")) {
                JSONObject jsonObject = MineruJsonSupport.parseObject(
                    new String(responseBytes, StandardCharsets.UTF_8),
                    "MinerU async result response: " + path
                );
                throw new DocumentParseException("MinerU async result is not ready: " + jsonObject.toJSONString());
            }
            if (responseBytes.length < 2 || responseBytes[0] != 'P' || responseBytes[1] != 'K') {
                throw new DocumentParseException("MinerU async result is not a valid ZIP payload");
            }
            return responseBytes;
        } catch (IOException exception) {
            throw new DocumentParseException("Failed to query MinerU result ZIP", exception);
        }
    }

    protected JSONObject executeJsonMultipart(String path, ParseRequest request, Map<String, List<String>> fields) {
        MultipartBody.Builder formBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
        appendFiles(formBuilder, request.getFiles());
        appendStringFields(formBuilder, fields);
        Request httpRequest = new Request.Builder()
            .url(baseUrl + path)
            .post(formBuilder.build())
            .build();
        return executeJsonRequest(path, httpRequest);
    }

    protected JSONObject executeJsonGet(String path) {
        Request request = new Request.Builder().url(baseUrl + path).get().build();
        return executeJsonRequest(path, request);
    }

    protected JSONObject executeJsonRequest(String path, Request request) {
        try (Response response = okHttpClient.newCall(request).execute()) {
            ResponseBody body = response.body();
            String bodyText = body == null ? "" : body.string();
            if (!response.isSuccessful()) {
                throw buildHttpException(
                    path,
                    response.code(),
                    bodyText == null ? new byte[0] : bodyText.getBytes(StandardCharsets.UTF_8)
                );
            }
            return MineruJsonSupport.parseObject(bodyText, "MinerU response body: " + path);
        } catch (IOException exception) {
            throw new DocumentParseException("Failed to call MinerU endpoint: " + path, exception);
        }
    }

    private void appendFiles(MultipartBody.Builder formBuilder, List<ParseFile> files) {
        if (files == null || files.isEmpty()) {
            throw new IllegalArgumentException("Parse request must contain at least one file");
        }
        for (ParseFile file : files) {
            if (file == null || !StringUtil.hasText(file.getFileName()) || file.getContent() == null) {
                throw new IllegalArgumentException("Parse request contains an invalid file");
            }
            MediaType mediaType = StringUtil.hasText(file.getContentType())
                ? MediaType.parse(file.getContentType())
                : detectMediaType(file.getFileName());
            formBuilder.addFormDataPart(
                "files",
                file.getFileName(),
                RequestBody.create(file.getContent(), mediaType)
            );
        }
    }

    private void appendStringFields(MultipartBody.Builder formBuilder, Map<String, List<String>> fields) {
        for (Map.Entry<String, List<String>> entry : fields.entrySet()) {
            if (entry.getValue() == null) {
                continue;
            }
            for (String value : entry.getValue()) {
                if (value != null) {
                    formBuilder.addFormDataPart(entry.getKey(), value);
                }
            }
        }
    }

    private Map<String, List<String>> buildSyncFormFields(ParseRequest request) {
        return mineruMapper.buildSyncFormFields(request);
    }

    private Map<String, List<String>> buildAsyncFormFields(ParseRequest request) {
        return mineruMapper.buildAsyncFormFields(request);
    }

    private DocumentParseException buildHttpException(String path, int statusCode, byte[] bodyBytes) {
        String bodyText = bodyBytes == null ? "" : new String(bodyBytes, StandardCharsets.UTF_8);
        return new DocumentParseException(
            "MinerU request failed: path=" + path + ", status=" + statusCode + ", body=" + bodyText
        );
    }

    private String normalizeBaseUrl(String baseUrl) {
        if (baseUrl.endsWith("/")) {
            return baseUrl.substring(0, baseUrl.length() - 1);
        }
        return baseUrl;
    }

    private MediaType detectMediaType(String fileName) {
        String mimeType = URLConnection.guessContentTypeFromName(fileName);
        return StringUtil.hasText(mimeType) ? MediaType.parse(mimeType) : DEFAULT_MEDIA_TYPE;
    }
}
