package com.adx.tracking.media.kuaishou;

import com.adx.tracking.baidu.model.PaymentInfo;
import com.adx.tracking.storage.model.BidRecord;
import com.adx.tracking.storage.model.MediaCallbackRecord;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.*;

/**
 * 对应 Go internal/media/kuaishou/client.go 的 Client。
 * 职责：
 *   1. 百度 act → 快手 eventType 的映射（默认表 + 可配置覆盖）
 *   2. 发送转化事件回调（buildCallbackURL + HTTP GET）
 *   3. 重试已有回调记录
 */
public class KuaishouClient {

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

    private final HttpClient httpClient;
    private final Map<Integer, Integer> actMap;        // baiduAct -> kuaishouEventType
    private final Map<Integer, List<Integer>> extraActMap; // baiduAct -> [extra kuaishouEventTypes]

    public KuaishouClient(Map<Integer, Integer> actMapOverride,
                          Map<Integer, List<Integer>> extraActMap) {
        this.httpClient = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(10))
                .build();
        // 从默认表开始，再用配置覆盖
        Map<Integer, Integer> merged = new HashMap<>(defaultActMap());
        if (actMapOverride != null) merged.putAll(actMapOverride);
        this.actMap = Collections.unmodifiableMap(merged);
        this.extraActMap = extraActMap != null ? extraActMap : Collections.emptyMap();
    }

    // ─── ConversionEventTypes ────────────────────────────────────────────────

    /**
     * 对应 Go ConversionEventTypes()：
     * 根据 payment.act 查出所有需要发送的快手事件类型（主+附加）。
     */
    public List<Integer> conversionEventTypes(PaymentInfo payment) {
        int eventType = actMap.getOrDefault(payment.getAct(), 0);
        if (eventType == 0) {
            throw new IllegalArgumentException("unsupported baidu act: " + payment.getAct());
        }
        Set<Integer> seen = new LinkedHashSet<>();
        seen.add(eventType);
        List<Integer> extras = extraActMap.getOrDefault(payment.getAct(), Collections.emptyList());
        for (int extra : extras) {
            if (extra != 0) seen.add(extra);
        }
        return new ArrayList<>(seen);
    }

    // ─── SendConversionEvent ─────────────────────────────────────────────────

    /**
     * 对应 Go SendConversionEvent()：构造回调 URL 并发送 GET 请求。
     * 与 Go 一致：即使出错也返回部分填充的 MediaCallbackRecord（而非抛异常），
     * 调用方可以获取 callbackUrl / eventTimeMs 等信息用于持久化。
     */
    public MediaCallbackRecord sendConversionEvent(BidRecord bid, PaymentInfo payment, int eventType) {
        String callbackUrl = bid.getMediaParams() != null
                ? bid.getMediaParams().get("callback") : null;
        if (callbackUrl == null || callbackUrl.isBlank()) {
            throw new IllegalArgumentException("kuaishou callback is required");
        }
        long eventTimeMs = Instant.now().toEpochMilli();

        MediaCallbackRecord record = new MediaCallbackRecord();
        record.setMedia("kuaishou");
        record.setQk(payment.getQk());
        record.setEventType(eventType);
        record.setEventTimeMs(eventTimeMs);
        record.setPurchase(payment.getPayment());
        record.setAttempt(1);
        record.setCreatedAt(Instant.now());

        String reportUrl;
        try {
            reportUrl = buildCallbackUrl(callbackUrl, eventType, eventTimeMs, payment.getPayment());
        } catch (Exception e) {
            // 对应 Go: buildCallbackURL 失败时返回 (emptyRecord, err)，调用方计入 Failed++
            record.setCallbackUrl(callbackUrl);
            record.setOk(false);
            record.setErrorMessage(e.getMessage());
            return record;
        }
        record.setCallbackUrl(reportUrl);

        try {
            log.info("[Kuaishou] sending callback: qk={}, eventType={}, url={}",
                    payment.getQk(), eventType, truncate(reportUrl, 200));
            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create(reportUrl))
                    .GET()
                    .timeout(Duration.ofSeconds(15))
                    .build();
            HttpResponse<byte[]> response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
            int status = response.statusCode();
            String body = truncate(new String(response.body()), 4096);
            record.setStatus(status);
            record.setOk(status >= 200 && status < 300);
            record.setResponseBody(body);
            log.info("[Kuaishou] callback result: qk={}, eventType={}, status={}, ok={}",
                    payment.getQk(), eventType, status, record.isOk());
        } catch (Exception e) {
            // 对应 Go：即使 HTTP 请求失败也返回 record（ErrorMessage 已设置），不抛异常
            record.setOk(false);
            record.setErrorMessage(e.getMessage());
            log.warn("[Kuaishou] callback failed: qk={}, eventType={}, error={}",
                    payment.getQk(), eventType, e.getMessage());
        }
        return record;
    }

    // ─── RetryCallback ───────────────────────────────────────────────────────

    /**
     * 对应 Go RetryCallback()：直接用原 callbackUrl 重发。
     */
    public MediaCallbackRecord retryCallback(MediaCallbackRecord original)
            throws IOException, InterruptedException {
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(original.getCallbackUrl()))
                .GET()
                .timeout(Duration.ofSeconds(15))
                .build();

        MediaCallbackRecord retry = copyRecord(original);
        retry.setAttempt(original.getAttempt() + 1);
        retry.setCreatedAt(Instant.now());

        try {
            log.info("[Kuaishou] retrying callback: dedupeKey={}, attempt={}, url={}",
                    original.getDedupeKey(), retry.getAttempt(), truncate(original.getCallbackUrl(), 200));
            HttpResponse<byte[]> response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
            int status = response.statusCode();
            retry.setStatus(status);
            retry.setOk(status >= 200 && status < 300);
            retry.setResponseBody(truncate(new String(response.body()), 4096));
            retry.setErrorMessage(null);
            log.info("[Kuaishou] retry result: dedupeKey={}, status={}, ok={}",
                    original.getDedupeKey(), status, retry.isOk());
        } catch (IOException | InterruptedException e) {
            retry.setOk(false);
            retry.setErrorMessage(e.getMessage());
            log.warn("[Kuaishou] retry failed: dedupeKey={}, error={}",
                    original.getDedupeKey(), e.getMessage());
            throw e;
        }
        return retry;
    }

    // ─── 构建回调 URL ─────────────────────────────────────────────────────────

    /**
     * 对应 Go buildCallbackURL()：
     * 在 URL 上设置 event_type, event_time, (purchase_amount if eventType==3)。
     * 与 Go 一致：使用 url.Values 风格的参数解析+重建，自动去重和正确编码。
     * 关键：Go 的 url.Values.Encode() 会按 key 字典序输出，
     * 这里使用 TreeMap 保证参数顺序与 Go 完全一致。
     * 注意：对应 Go 的 parsed.Query()（自动解码 percent-encoding）+ query.Encode()（重新编码），
     * 必须先解码再编码，避免双重编码。
     */
    static String buildCallbackUrl(String callbackUrl, int eventType, long eventTimeMs, double purchase) {
        try {
            URI uri = new URI(callbackUrl);
            // 解析已有 query 参数到 Map（对应 Go parsed.Query()，自动解码 percent-encoding）
            // 使用 TreeMap 按 key 字典序排序，与 Go url.Values.Encode() 行为一致
            Map<String, String> params = parseQueryDecoded(uri.getRawQuery());
            // Set 操作：已有则覆盖，没有则新增（对应 Go query.Set()）
            params.put("event_type", String.valueOf(eventType));
            params.put("event_time", String.valueOf(eventTimeMs));
            if (eventType == 3) {
                params.put("purchase_amount", String.format("%.2f", purchase));
            }
            // 重建 query string（对应 Go query.Encode()，按 key 排序输出）
            StringBuilder query = new StringBuilder();
            for (Map.Entry<String, String> e : params.entrySet()) {
                if (query.length() > 0) query.append("&");
                query.append(URLEncoder.encode(e.getKey(), StandardCharsets.UTF_8))
                      .append("=")
                      .append(URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8));
            }
            // 使用原始编码方式构建 URI，避免二次编码
            String result = uri.getScheme() + "://" + uri.getRawAuthority() + uri.getRawPath()
                    + "?" + query;
            if (uri.getRawFragment() != null) {
                result += "#" + uri.getRawFragment();
            }
            return result;
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException("invalid callback url: " + callbackUrl, e);
        }
    }
    
    /**
     * 解析 URL query string 为 TreeMap，并对值进行 percent-decode。
     * 对应 Go 的 url.Parse().Query()：返回解码后的 url.Values。
     */
    private static Map<String, String> parseQueryDecoded(String query) {
        Map<String, String> params = new TreeMap<>();
        if (query == null || query.isEmpty()) return params;
        for (String pair : query.split("&")) {
            int eq = pair.indexOf('=');
            if (eq > 0) {
                String key = java.net.URLDecoder.decode(pair.substring(0, eq), StandardCharsets.UTF_8);
                String value = java.net.URLDecoder.decode(pair.substring(eq + 1), StandardCharsets.UTF_8);
                params.put(key, value);
            } else if (!pair.isEmpty()) {
                String key = java.net.URLDecoder.decode(pair, StandardCharsets.UTF_8);
                params.put(key, "");
            }
        }
        return params;
    }

    // ─── 默认 act 映射表（对应 Go defaultActMap()）────────────────────────────

    static Map<Integer, Integer> defaultActMap() {
        Map<Integer, Integer> m = new HashMap<>();
        m.put(1, 1);
        m.put(2, 3);
        m.put(3, 2);
        m.put(4, 7);
        m.put(5, 3);
        m.put(6, 84);
        m.put(8, 8);
        return m;
    }

    // ─── 工具 ────────────────────────────────────────────────────────────────

    private static String truncate(String s, int maxLen) {
        if (s == null) return null;
        return s.length() <= maxLen ? s : s.substring(0, maxLen);
    }

    private static MediaCallbackRecord copyRecord(MediaCallbackRecord src) {
        MediaCallbackRecord copy = new MediaCallbackRecord();
        copy.setDedupeKey(src.getDedupeKey());
        copy.setMedia(src.getMedia());
        copy.setQk(src.getQk());
        copy.setCallbackUrl(src.getCallbackUrl());
        copy.setEventType(src.getEventType());
        copy.setEventTimeMs(src.getEventTimeMs());
        copy.setPurchase(src.getPurchase());
        copy.setStatus(src.getStatus());
        copy.setOk(src.isOk());
        copy.setAttempt(src.getAttempt());
        copy.setResponseBody(src.getResponseBody());
        copy.setErrorMessage(src.getErrorMessage());
        copy.setCreatedAt(src.getCreatedAt());
        return copy;
    }
}
