package com.huimv.employment.integration.kb;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.huimv.employment.common.exception.BizException;
import com.huimv.employment.common.exception.ErrorCode;
import com.huimv.employment.integration.kb.rag.RagFileListQuery;
import com.huimv.employment.integration.kb.rag.RagInsertTextRequest;
import com.huimv.employment.integration.kb.rag.RagRetrieveRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.function.Supplier;

/**
 * RAG 知识库 {@code /api/rag/**} HTTP 客户端实现。
 */
@Service
public class KnowledgeBaseClientImpl implements KnowledgeBaseClient {

    private static final Logger log = LoggerFactory.getLogger(KnowledgeBaseClientImpl.class);

    private static final String RAG_PREFIX = "/api/rag";

    private static final String[] TRACK_ID_KEYS = {
            "track_id", "trackId", "file_id", "fileId", "kbDocId", "kb_doc_id", "docId", "id"
    };

    private final KbApiProperties properties;
    private final RestTemplate restTemplate;
    private final ObjectMapper objectMapper;

    public KnowledgeBaseClientImpl(KbApiProperties properties,
                                   @Qualifier("kbRestTemplate") RestTemplate restTemplate,
                                   ObjectMapper objectMapper) {
        this.properties = properties;
        this.restTemplate = restTemplate;
        this.objectMapper = objectMapper;
    }

    @Override
    public JsonNode health() {
        ensureReady();
        return getJson("/health");
    }

    @Override
    public String uploadFile(File file, String uploadFilename) {
        ensureReady();
        if (file == null || !file.isFile()) {
            throw new BizException(ErrorCode.BAD_REQUEST, "上传文件无效或不存在");
        }
        String name = StringUtils.hasText(uploadFilename) ? uploadFilename.trim() : file.getName();
        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
        body.add("file", multipartFileResource(file, name));
        return parseTrackId(postMultipart("/files", body));
    }

    @Override
    public String updateFile(String fileId, File file, String uploadFilename) {
        ensureReady();
        requireFileId(fileId);
        if (file == null || !file.isFile()) {
            throw new BizException(ErrorCode.BAD_REQUEST, "上传文件无效或不存在");
        }
        String name = StringUtils.hasText(uploadFilename) ? uploadFilename.trim() : file.getName();
        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
        body.add("file", multipartFileResource(file, name));
        return parseTrackId(putMultipart("/files/" + encodePathSegment(fileId), body));
    }

    @Override
    public JsonNode listFiles(RagFileListQuery query) {
        ensureReady();
        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(ragUrl("/files"));
        if (query != null) {
            if (query.getPage() != null) {
                builder.queryParam("page", query.getPage());
            }
            if (query.getPageSize() != null) {
                builder.queryParam("page_size", query.getPageSize());
            }
            if (StringUtils.hasText(query.getSortField())) {
                builder.queryParam("sort_field", query.getSortField());
            }
            if (StringUtils.hasText(query.getSortDirection())) {
                builder.queryParam("sort_direction", query.getSortDirection());
            }
        }
        return getJsonUri(builder.build(true).toUri());
    }

    @Override
    public JsonNode getFileStatus(String fileId) {
        ensureReady();
        requireFileId(fileId);
        return getJson("/files/" + encodePathSegment(fileId));
    }

    @Override
    public void deleteFile(String fileId) {
        ensureReady();
        requireFileId(fileId);
        String path = "/files/" + encodePathSegment(fileId);
        executeWithRetry("RAG 删除文档", () -> {
            try {
                HttpEntity<Void> entity = new HttpEntity<>(authHeaders());
                ResponseEntity<String> resp = restTemplate.exchange(
                        URI.create(ragUrl(path)), HttpMethod.DELETE, entity, String.class);
                if (!resp.getStatusCode().is2xxSuccessful()) {
                    throw new BizException(ErrorCode.KB_SERVICE_FAILED,
                            "RAG 删除失败 HTTP " + resp.getStatusCode().value());
                }
                return null;
            } catch (HttpStatusCodeException e) {
                if (e.getRawStatusCode() == 404) {
                    return null;
                }
                throw httpError("RAG 删除失败", e);
            }
        });
    }

    @Override
    public JsonNode retrieve(RagRetrieveRequest request) {
        ensureReady();
        if (request == null || !StringUtils.hasText(request.getQuery())) {
            throw new BizException(ErrorCode.BAD_REQUEST, "检索 query 不能为空");
        }
        return postJson("/retrieve", request);
    }

    @Override
    public String insertText(String text, String fileSource) {
        ensureReady();
        if (!StringUtils.hasText(text)) {
            throw new BizException(ErrorCode.BAD_REQUEST, "text 不能为空");
        }
        if (!StringUtils.hasText(fileSource)) {
            throw new BizException(ErrorCode.BAD_REQUEST, "file_source 不能为空");
        }
        RagInsertTextRequest body = new RagInsertTextRequest(text.trim(), fileSource.trim());
        return parseTrackId(postJsonRaw("/insert-text", body));
    }

    @Override
    public JsonNode reprocessFailed() {
        ensureReady();
        return postJson("/reprocess-failed", new Object());
    }

    private JsonNode getJson(String path) {
        return getJsonUri(URI.create(ragUrl(path)));
    }

    private JsonNode getJsonUri(URI uri) {
        String raw = executeWithRetry("RAG GET " + uri.getPath(), () -> {
            try {
                HttpEntity<Void> entity = new HttpEntity<>(authHeaders());
                ResponseEntity<String> resp = restTemplate.exchange(uri, HttpMethod.GET, entity, String.class);
                return resp.getBody();
            } catch (HttpStatusCodeException e) {
                throw httpError("RAG 请求失败", e);
            }
        });
        return parseJsonNode(raw);
    }

    private JsonNode postJson(String path, Object body) {
        return parseJsonNode(postJsonRaw(path, body));
    }

    private String postJsonRaw(String path, Object body) {
        return executeWithRetry("RAG POST " + path, () -> {
            try {
                HttpHeaders headers = authHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                HttpEntity<Object> entity = new HttpEntity<>(body, headers);
                ResponseEntity<String> resp = restTemplate.exchange(
                        URI.create(ragUrl(path)), HttpMethod.POST, entity, String.class);
                return resp.getBody();
            } catch (HttpStatusCodeException e) {
                throw httpError("RAG 请求失败", e);
            }
        });
    }

    private String postMultipart(String path, MultiValueMap<String, Object> body) {
        return executeWithRetry("RAG 上传", () -> {
            try {
                HttpHeaders headers = authHeaders();
                HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers);
                ResponseEntity<String> resp = restTemplate.exchange(
                        URI.create(ragUrl(path)), HttpMethod.POST, entity, String.class);
                return resp.getBody();
            } catch (HttpStatusCodeException e) {
                throw httpError("RAG 上传失败", e);
            }
        });
    }

    private String putMultipart(String path, MultiValueMap<String, Object> body) {
        return executeWithRetry("RAG 更新文件", () -> {
            try {
                HttpHeaders headers = authHeaders();
                HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers);
                ResponseEntity<String> resp = restTemplate.exchange(
                        URI.create(ragUrl(path)), HttpMethod.PUT, entity, String.class);
                return resp.getBody();
            } catch (HttpStatusCodeException e) {
                throw httpError("RAG 更新失败", e);
            }
        });
    }

    private HttpHeaders authHeaders() {
        HttpHeaders headers = new HttpHeaders();
        properties.applyAuthHeader(headers);
        return headers;
    }

    private String ragUrl(String path) {
        return properties.baseUrl() + RAG_PREFIX + path;
    }

    private static FileSystemResource multipartFileResource(File file, String uploadName) {
        return new FileSystemResource(file) {
            @Override
            public String getFilename() {
                return uploadName;
            }
        };
    }

    private static void requireFileId(String fileId) {
        if (!StringUtils.hasText(fileId)) {
            throw new BizException(ErrorCode.BAD_REQUEST, "file_id 不能为空");
        }
    }

    private JsonNode parseJsonNode(String raw) {
        if (!StringUtils.hasText(raw)) {
            throw new BizException(ErrorCode.KB_SERVICE_FAILED, "RAG 返回空响应");
        }
        try {
            JsonNode root = objectMapper.readTree(raw);
            assertBusinessOk(root);
            return root;
        } catch (BizException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new BizException(ErrorCode.KB_SERVICE_FAILED, "RAG 响应非 JSON：" + abbreviate(raw));
        }
    }

    private String parseTrackId(String raw) {
        JsonNode root = parseJsonNode(raw);
        String trackId = extractTrackId(root);
        if (!StringUtils.hasText(trackId)) {
            throw new BizException(ErrorCode.KB_SERVICE_FAILED,
                    "RAG 成功但未解析到 track_id，响应：" + abbreviate(raw));
        }
        return trackId;
    }

    private void assertBusinessOk(JsonNode root) {
        if (root == null || !root.has("code")) {
            return;
        }
        JsonNode c = root.get("code");
        boolean ok = false;
        if (c.isNumber()) {
            int v = c.intValue();
            ok = (v == 0 || v == 200);
        } else if (c.isTextual()) {
            String s = c.asText();
            ok = "0".equals(s) || "200".equals(s) || "success".equalsIgnoreCase(s);
        }
        if (!ok) {
            String msg = root.has("message") ? root.get("message").asText(null) : null;
            if (!StringUtils.hasText(msg) && root.has("msg")) {
                msg = root.get("msg").asText(null);
            }
            if (!StringUtils.hasText(msg)) {
                msg = "RAG 接口返回失败";
            }
            throw new BizException(ErrorCode.KB_SERVICE_FAILED, msg);
        }
    }

    private static String extractTrackId(JsonNode root) {
        JsonNode data = root.get("data");
        if (data != null && data.isTextual()) {
            String t = data.asText(null);
            if (StringUtils.hasText(t)) {
                return t;
            }
        }
        if (data != null && data.isObject()) {
            for (String key : TRACK_ID_KEYS) {
                if (data.has(key) && !data.get(key).isNull()) {
                    String v = data.get(key).asText(null);
                    if (StringUtils.hasText(v)) {
                        return v;
                    }
                }
            }
        }
        for (String key : TRACK_ID_KEYS) {
            if (root.has(key) && !root.get(key).isNull()) {
                String v = root.get(key).asText(null);
                if (StringUtils.hasText(v)) {
                    return v;
                }
            }
        }
        return null;
    }

    private BizException httpError(String prefix, HttpStatusCodeException e) {
        String b = e.getResponseBodyAsString(StandardCharsets.UTF_8);
        return new BizException(ErrorCode.KB_SERVICE_FAILED,
                prefix + " HTTP " + e.getRawStatusCode() + "：" + abbreviate(b));
    }

    private <T> T executeWithRetry(String operation, Supplier<T> action) {
        int maxRetries = Math.max(0, properties.getMaxRetries());
        BizException last = null;
        for (int attempt = 0; attempt <= maxRetries; attempt++) {
            try {
                return action.get();
            } catch (BizException e) {
                if (attempt >= maxRetries || !isRetryable(e)) {
                    throw e;
                }
                last = e;
                log.warn("{}失败，第 {}/{} 次重试：{}", operation, attempt + 1, maxRetries, e.getMessage());
                sleepBeforeRetry();
            } catch (Exception e) {
                BizException wrapped = wrapUnexpected(operation, e);
                if (attempt >= maxRetries || !isRetryable(wrapped, e)) {
                    throw wrapped;
                }
                last = wrapped;
                log.warn("{}异常，第 {}/{} 次重试：{}", operation, attempt + 1, maxRetries, e.getMessage());
                sleepBeforeRetry();
            }
        }
        if (last != null) {
            throw last;
        }
        throw new BizException(ErrorCode.KB_SERVICE_FAILED, operation + "失败");
    }

    private static boolean isRetryable(BizException e) {
        String msg = e.getMessage();
        if (msg == null) {
            return false;
        }
        if (msg.contains("HTTP 404")) {
            return false;
        }
        if (msg.contains("HTTP 408") || msg.contains("HTTP 429")) {
            return true;
        }
        if (msg.matches(".*HTTP [45]\\d\\d.*")) {
            int code = extractHttpStatus(msg);
            return code >= 500 || code == 408 || code == 429;
        }
        return false;
    }

    private static boolean isRetryable(BizException wrapped, Exception cause) {
        if (cause instanceof ResourceAccessException) {
            return true;
        }
        return isRetryable(wrapped);
    }

    private static int extractHttpStatus(String msg) {
        int idx = msg.indexOf("HTTP ");
        if (idx < 0) {
            return 0;
        }
        int start = idx + 5;
        int end = start;
        while (end < msg.length() && Character.isDigit(msg.charAt(end))) {
            end++;
        }
        if (end == start) {
            return 0;
        }
        try {
            return Integer.parseInt(msg.substring(start, end));
        } catch (NumberFormatException e) {
            return 0;
        }
    }

    private void sleepBeforeRetry() {
        int interval = Math.max(0, properties.getRetryIntervalMs());
        if (interval <= 0) {
            return;
        }
        try {
            Thread.sleep(interval);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private static BizException wrapUnexpected(String operation, Exception e) {
        if (e instanceof BizException) {
            return (BizException) e;
        }
        return new BizException(ErrorCode.KB_SERVICE_FAILED, operation + "异常：" + e.getMessage());
    }

    private static String encodePathSegment(String id) {
        try {
            return URLEncoder.encode(id, StandardCharsets.UTF_8.name()).replace("+", "%20");
        } catch (UnsupportedEncodingException e) {
            return id;
        }
    }

    private static String abbreviate(String s) {
        if (s == null) {
            return "";
        }
        String t = s.trim();
        if (t.length() > 500) {
            return t.substring(0, 500) + "...";
        }
        return t;
    }

    private void ensureReady() {
        if (!properties.isEnabled()) {
            throw new BizException(ErrorCode.KB_SERVICE_DISABLED);
        }
    }
}
