package com.emoon.tongue.service;

import cn.hutool.json.JSONUtil;
import com.emoon.common.core.domain.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.ByteArrayOutputStream;
import java.net.http.HttpClient;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Map;

/**
 * 转发服务（机器人端）：把公网请求转发到内网的同名服务，并将响应原样透传回去。
 *
 * 默认通过 forward.target-base-url 指定内网服务根地址，例如：
 * - http://192.168.10.200:8082
 *
 * 不配置也能启动（默认 127.0.0.1），但通常你需要在公网部署时显式配置它。
 */
@Slf4j
@Service
public class ForwardRobotService {

    /**
     * 内网目标服务根地址（不带结尾 /）
     */
    @Value("${forward.target-base-url:http://61.178.77.37:8100/service5/robot}")
    private String targetBaseUrl;

    /**
     * 某些上游 Nginx/网关会基于 Host 做路由/白名单；若 Host 不匹配可能直接丢连接（表现为 "received no bytes"）。
     * 为空则不强制设置 Host（使用默认 Host=目标域名/IP）。
     *
     * 示例：
     * forward.target-host-header: test.hanmc.online
     */
    @Value("${forward.target-host-header:}")
    private String targetHostHeader;

    /**
     * 某些网关/上游对短连接策略更"敏感"，有时强制 close 反而更稳（也可能更不稳）。
     * 这里做成可配置开关，便于现场快速试错定位。
     */
    @Value("${forward.force-connection-close:false}")
    private boolean forceConnectionClose;

    /**
     * 连接/读超时（毫秒）
     */
    @Value("${forward.connect-timeout-ms:5000}")
    private int connectTimeoutMs;

    @Value("${forward.read-timeout-ms:60000}")
    private int readTimeoutMs;

    @Value("${forward.user-agent:curl/7.81.0}")
    private String userAgent;

    private RestTemplate buildRestTemplate() {
        // 避免默认 HttpURLConnection 在 multipart/反代链路下出现 EOF/断流等兼容性问题：
        // 使用 JDK HttpClient（HTTP/1.1）作为底层实现，超时可控，稳定性更好。
        HttpClient client = HttpClient.newBuilder()
            .connectTimeout(Duration.ofMillis(connectTimeoutMs))
            .version(HttpClient.Version.HTTP_1_1)
            .build();
        JdkClientHttpRequestFactory rf = new JdkClientHttpRequestFactory(client);
        rf.setReadTimeout(Duration.ofMillis(readTimeoutMs));
        // 说明：当前 Spring 版本的 JdkClientHttpRequestFactory 不提供 setBufferRequestBody 开关；
        // multipart 是否缓冲主要取决于消息转换器与 Resource 实现。
        // 这里通过下方 file Resource 的 InputStream 提供方式，尽量避免 file.getBytes() 全量读入。
        return new RestTemplate(rf);
    }

    /**
     * 兜底：某些上游/Nginx/WAF 对 JDK HttpClient 的报文兼容性较差，会直接断连接（表现为"header parser received no bytes"）。
     * HttpURLConnection 在这类链路上往往更"宽容"。
     */
    private RestTemplate buildSimpleRestTemplate() {
        SimpleClientHttpRequestFactory rf = new SimpleClientHttpRequestFactory();
        rf.setConnectTimeout(connectTimeoutMs);
        rf.setReadTimeout(readTimeoutMs);
        // 默认会缓冲请求体（byte[]），从而走 fixed-length（带 Content-Length），更接近 curl 行为
        return new RestTemplate(rf);
    }

    private String joinUrl(String base, String path) {
        String b = (base == null) ? "" : base.trim();
        String p = (path == null) ? "" : path.trim();
        if (b.endsWith("/")) b = b.substring(0, b.length() - 1);
        if (!p.startsWith("/")) p = "/" + p;
        return b + p;
    }

    private ResponseEntity<String> toJsonResponse(ResponseEntity<String> upstream) {
        if (upstream == null) {
            return ResponseEntity.status(HttpStatus.BAD_GATEWAY)
                .contentType(MediaType.APPLICATION_JSON)
                .body(JSONUtil.toJsonStr(R.fail("转发失败：上游无响应")));
        }
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        return new ResponseEntity<>(upstream.getBody(), headers, upstream.getStatusCode());
    }

    public ResponseEntity<String> forwardCheckImage(MultipartFile file,
                                                    String patientId,
                                                    String projectId,
                                                    Long timestamp,
                                                    String sign,
                                                    String version,
                                                    String bizParams) {
        String url = joinUrl(targetBaseUrl, "/api/v1/diagnosis/robot/check-image");

        try {
            long start = System.currentTimeMillis();
            long fileSize = (file != null ? file.getSize() : 0L);
            String ct = (file != null ? file.getContentType() : null);
            log.info("转发 check-image 开始（机器人端），url={}, fileSize={} bytes, contentType={}", url, fileSize, ct);

            // 兼容性优先：手工构造 multipart（固定 Content-Length），避免 chunked 传输在某些 Nginx/上游组合下被直接断流
            final String boundary = "----EmoonForwardBoundary" + System.currentTimeMillis();
            final byte[] CRLF = "\r\n".getBytes(StandardCharsets.UTF_8);

            String filename = (file != null && file.getOriginalFilename() != null) ? file.getOriginalFilename() : "file";
            String fileContentType = (file != null && file.getContentType() != null && !file.getContentType().isBlank())
                ? file.getContentType()
                : MediaType.APPLICATION_OCTET_STREAM_VALUE;
            byte[] fileBytes = (file != null) ? file.getBytes() : new byte[0];

            ByteArrayOutputStream bos = new ByteArrayOutputStream(fileBytes.length + 4096);

            // helper: write one form field
            java.util.function.BiConsumer<String, String> writeField = (name, value) -> {
                try {
                    bos.write(("--" + boundary).getBytes(StandardCharsets.UTF_8));
                    bos.write(CRLF);
                    bos.write(("Content-Disposition: form-data; name=\"" + name + "\"").getBytes(StandardCharsets.UTF_8));
                    bos.write(CRLF);
                    bos.write(CRLF);
                    bos.write((value == null ? "" : value).getBytes(StandardCharsets.UTF_8));
                    bos.write(CRLF);
                } catch (Exception e) {
                    throw new IllegalStateException("构造 multipart 字段失败: " + name, e);
                }
            };

            // form fields (names must match original controller)
            writeField.accept("patientId", patientId);
            writeField.accept("projectId", projectId);
            writeField.accept("timestamp", timestamp != null ? String.valueOf(timestamp) : "");
            writeField.accept("sign", sign);
            writeField.accept("version", version);
            writeField.accept("bizParams", bizParams);

            // file field
            bos.write(("--" + boundary).getBytes(StandardCharsets.UTF_8));
            bos.write(CRLF);
            bos.write(("Content-Disposition: form-data; name=\"file\"; filename=\"" + filename + "\"").getBytes(StandardCharsets.UTF_8));
            bos.write(CRLF);
            bos.write(("Content-Type: " + fileContentType).getBytes(StandardCharsets.UTF_8));
            bos.write(CRLF);
            bos.write(CRLF);
            bos.write(fileBytes);
            bos.write(CRLF);

            // end
            bos.write(("--" + boundary + "--").getBytes(StandardCharsets.UTF_8));
            bos.write(CRLF);

            byte[] multipartBody = bos.toByteArray();

            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.parseMediaType("multipart/form-data; boundary=" + boundary));
            headers.setContentLength(multipartBody.length);
            // 尽量模拟 curl：Accept */* + User-Agent=curl/...
            headers.setAccept(java.util.List.of(MediaType.ALL));
            if (userAgent != null && !userAgent.isBlank()) {
                headers.set(HttpHeaders.USER_AGENT, userAgent.trim());
            }
            if (targetHostHeader != null && !targetHostHeader.isBlank()) {
                headers.set(HttpHeaders.HOST, targetHostHeader.trim());
            }
            if (forceConnectionClose) {
                headers.setConnection("close");
            }

            HttpEntity<byte[]> req = new HttpEntity<>(multipartBody, headers);
            ResponseEntity<String> resp;
            try {
                resp = buildRestTemplate().exchange(url, HttpMethod.POST, req, String.class);
            } catch (ResourceAccessException e) {
                // 典型场景：java.net.http 报 EOF / "header parser received no bytes"
                log.warn("转发 check-image JDK HttpClient 失败，尝试降级为 HttpURLConnection 重试，url={}, reason={}",
                    url, e.getMessage());
                resp = buildSimpleRestTemplate().exchange(url, HttpMethod.POST, req, String.class);
            }
            log.info("转发 check-image 完成（机器人端），url={}, status={}, costMs={}", url,
                resp != null ? resp.getStatusCode() : null, (System.currentTimeMillis() - start));
            return toJsonResponse(resp);
        } catch (Exception e) {
            log.error("转发 check-image 失败（机器人端），url={}", url, e);
            return ResponseEntity.status(HttpStatus.BAD_GATEWAY)
                .contentType(MediaType.APPLICATION_JSON)
                .body(JSONUtil.toJsonStr(R.fail("转发失败：" + e.getMessage())));
        }
    }

    public ResponseEntity<String> forwardDetail(String patientId,
                                                String projectId,
                                                Long timestamp,
                                                String version,
                                                String sign) {
        String base = joinUrl(targetBaseUrl, "/api/v1/diagnosis/robot/detail");
        String url = UriComponentsBuilder.fromHttpUrl(base)
            .queryParam("patientId", patientId)
            .queryParam("projectId", projectId)
            .queryParam("timestamp", timestamp)
            .queryParam("version", version)
            .queryParam("sign", sign)
            .toUriString();

        try {
            HttpHeaders headers = new HttpHeaders();
            headers.setAccept(java.util.List.of(MediaType.APPLICATION_JSON, MediaType.ALL));
            if (targetHostHeader != null && !targetHostHeader.isBlank()) {
                headers.set(HttpHeaders.HOST, targetHostHeader.trim());
            }
            ResponseEntity<String> resp = buildRestTemplate().exchange(url, HttpMethod.GET, new HttpEntity<>(headers), String.class);
            return toJsonResponse(resp);
        } catch (RestClientException e) {
            log.error("转发 detail 失败（机器人端），url={}", url, e);
            return ResponseEntity.status(HttpStatus.BAD_GATEWAY)
                .contentType(MediaType.APPLICATION_JSON)
                .body(JSONUtil.toJsonStr(R.fail("转发失败：" + e.getMessage())));
        }
    }

    public ResponseEntity<String> forwardAiDiagnosis(String patientId,
                                                     String projectId,
                                                     Long timestamp,
                                                     String version,
                                                     String sign) {
        String url = joinUrl(targetBaseUrl, "/api/v1/diagnosis/robot/ai-diagnosis");

        try {
            MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
            form.add("patientId", patientId);
            form.add("projectId", projectId);
            form.add("timestamp", timestamp != null ? String.valueOf(timestamp) : "");
            form.add("version", version);
            form.add("sign", sign);

            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
            headers.setAccept(java.util.List.of(MediaType.APPLICATION_JSON, MediaType.ALL));
            if (targetHostHeader != null && !targetHostHeader.isBlank()) {
                headers.set(HttpHeaders.HOST, targetHostHeader.trim());
            }
            HttpEntity<MultiValueMap<String, String>> req = new HttpEntity<>(form, headers);

            ResponseEntity<String> resp = buildRestTemplate().postForEntity(url, req, String.class);
            return toJsonResponse(resp);
        } catch (Exception e) {
            log.error("转发 ai-diagnosis 失败（机器人端），url={}", url, e);
            return ResponseEntity.status(HttpStatus.BAD_GATEWAY)
                .contentType(MediaType.APPLICATION_JSON)
                .body(JSONUtil.toJsonStr(R.fail("转发失败：" + e.getMessage())));
        }
    }

    /**
     * 通用：转发 JSON POST（用于 /llm-call、/generate-sign 等）
     */
    public ResponseEntity<String> forwardJsonPost(String path, Object bodyObj) {
        String url = joinUrl(targetBaseUrl, path);
        try {
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            headers.setAccept(java.util.List.of(MediaType.APPLICATION_JSON, MediaType.ALL));
            if (targetHostHeader != null && !targetHostHeader.isBlank()) {
                headers.set(HttpHeaders.HOST, targetHostHeader.trim());
            }
            String json = JSONUtil.toJsonStr(bodyObj);
            HttpEntity<String> req = new HttpEntity<>(json, headers);
            ResponseEntity<String> resp = buildRestTemplate().postForEntity(url, req, String.class);
            return toJsonResponse(resp);
        } catch (Exception e) {
            log.error("转发 JSON POST 失败（机器人端），url={}", url, e);
            return ResponseEntity.status(HttpStatus.BAD_GATEWAY)
                .contentType(MediaType.APPLICATION_JSON)
                .body(JSONUtil.toJsonStr(R.fail("转发失败：" + e.getMessage())));
        }
    }
}




