package com.jeesharp.business.modules.setting.application;

import com.jeesharp.business.modules.setting.enums.PrivacyPolicyClientEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 隐私政策「端适配结果」Redis 缓存。
 * <p>
 * key 规则：
 * <ul>
 *     <li>{@code biz:pp:adapt:{id}:{client}} → 按 id + 端 缓存的适配后 HTML；</li>
 *     <li>{@code biz:pp:docCodeId:{DOCCODE}} → 启用中文档编号到主键 id 的映射，避免每次按 code 查询都打 DB。</li>
 * </ul>
 * 失效时机：保存、删除、启用/禁用时按 id 一并清掉三端 adapt key 与 docCode→id 映射。
 * PC 端只是原文，不走缓存；超长 content（默认 256KB）跳过写入，避免占内存。
 * 任意 Redis 异常都仅打日志、不抛出，调用方可继续走实时适配/DB。
 *
 * @author jeesharp
 * @since 2026-05-29
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class PrivacyPolicyCacheService {

    private static final String KEY_ADAPT_PREFIX = "biz:pp:adapt:";
    private static final String KEY_DOC_CODE_ID_PREFIX = "biz:pp:docCodeId:";

    /** 端适配结果缓存 TTL。 */
    @Value("${jeesharp.business.privacy-policy.cache.adapt-ttl-seconds:86400}")
    private long adaptTtlSeconds;

    /** docCode→id 缓存 TTL。 */
    @Value("${jeesharp.business.privacy-policy.cache.code-ttl-seconds:86400}")
    private long codeTtlSeconds;

    /** 超过该长度的内容跳过缓存，单位字符；默认 256K。 */
    @Value("${jeesharp.business.privacy-policy.cache.max-cacheable-length:262144}")
    private int maxCacheableLength;

    private final StringRedisTemplate redisTemplate;

    /* ---------- adapt(id + client) ---------- */

    /**
     * 读取「按端适配后的 HTML」。PC 端不走缓存，直接返回 null 让调用方走原文。
     */
    public String getAdapted(String id, PrivacyPolicyClientEnum client) {
        if (id == null || id.isBlank() || client == null || client == PrivacyPolicyClientEnum.PC) {
            return null;
        }
        try {
            return redisTemplate.opsForValue().get(buildAdaptKey(id, client));
        } catch (Exception e) {
            log.warn("[PrivacyPolicyCache] get adapt failed, id={}, client={}, msg={}", id, client, e.getMessage());
            return null;
        }
    }

    /**
     * 写入「按端适配后的 HTML」。PC 端 / 空 / 超长内容均跳过。
     */
    public void putAdapted(String id, PrivacyPolicyClientEnum client, String adaptedHtml) {
        if (id == null || id.isBlank() || client == null || client == PrivacyPolicyClientEnum.PC) {
            return;
        }
        if (adaptedHtml == null) {
            return;
        }
        if (adaptedHtml.length() > maxCacheableLength) {
            log.debug("[PrivacyPolicyCache] skip large content, id={}, client={}, length={}",
                    id, client, adaptedHtml.length());
            return;
        }
        try {
            redisTemplate.opsForValue().set(
                    buildAdaptKey(id, client), adaptedHtml, Duration.ofSeconds(Math.max(1, adaptTtlSeconds)));
        } catch (Exception e) {
            log.warn("[PrivacyPolicyCache] put adapt failed, id={}, client={}, msg={}", id, client, e.getMessage());
        }
    }

    /**
     * 按 id 失效所有端的适配缓存（PC 实际未写入，但一并 del 不会出错）。
     */
    public void evictById(String id) {
        if (id == null || id.isBlank()) {
            return;
        }
        try {
            List<String> keys = new ArrayList<>(PrivacyPolicyClientEnum.values().length);
            for (PrivacyPolicyClientEnum client : PrivacyPolicyClientEnum.values()) {
                keys.add(buildAdaptKey(id, client));
            }
            redisTemplate.delete(keys);
        } catch (Exception e) {
            log.warn("[PrivacyPolicyCache] evict by id failed, id={}, msg={}", id, e.getMessage());
        }
    }

    /**
     * 批量按 id 失效。
     */
    public void evictByIds(List<String> ids) {
        if (ids == null || ids.isEmpty()) {
            return;
        }
        for (String id : ids) {
            evictById(id);
        }
    }

    /* ---------- docCode → id ---------- */

    /**
     * 读取启用中文档编号对应的主键 id。
     */
    public String getEnabledIdByDocCode(String docCode) {
        String normalized = normalizeDocCode(docCode);
        if (normalized == null) {
            return null;
        }
        try {
            return redisTemplate.opsForValue().get(buildDocCodeKey(normalized));
        } catch (Exception e) {
            log.warn("[PrivacyPolicyCache] get docCodeId failed, code={}, msg={}", normalized, e.getMessage());
            return null;
        }
    }

    public void putEnabledIdByDocCode(String docCode, String id) {
        String normalized = normalizeDocCode(docCode);
        if (normalized == null || id == null || id.isBlank()) {
            return;
        }
        try {
            redisTemplate.opsForValue().set(
                    buildDocCodeKey(normalized), id, Duration.ofSeconds(Math.max(1, codeTtlSeconds)));
        } catch (Exception e) {
            log.warn("[PrivacyPolicyCache] put docCodeId failed, code={}, msg={}", normalized, e.getMessage());
        }
    }

    public void evictDocCode(String docCode) {
        String normalized = normalizeDocCode(docCode);
        if (normalized == null) {
            return;
        }
        try {
            redisTemplate.delete(buildDocCodeKey(normalized));
        } catch (Exception e) {
            log.warn("[PrivacyPolicyCache] evict docCode failed, code={}, msg={}", normalized, e.getMessage());
        }
    }

    /**
     * 同时失效一批 docCode 与对应 id 的缓存。
     */
    public void evictByDocCodesAndIds(List<String> docCodes, List<String> ids) {
        if (docCodes != null) {
            for (String code : docCodes) {
                evictDocCode(code);
            }
        }
        evictByIds(ids);
    }

    /* ---------- helpers ---------- */

    private static String buildAdaptKey(String id, PrivacyPolicyClientEnum client) {
        return KEY_ADAPT_PREFIX + id + ":" + client.getCode();
    }

    private static String buildDocCodeKey(String normalizedDocCode) {
        return KEY_DOC_CODE_ID_PREFIX + normalizedDocCode;
    }

    private static String normalizeDocCode(String docCode) {
        if (docCode == null) {
            return null;
        }
        String s = docCode.trim();
        if (s.isEmpty()) {
            return null;
        }
        return s.toUpperCase();
    }

    /**
     * 给 ApplicationService 一个便捷方法：把字符串 ids 解析为 List，避免重复样板。
     */
    public static List<String> splitIds(String ids) {
        if (ids == null || ids.isBlank()) {
            return List.of();
        }
        List<String> out = new ArrayList<>();
        for (String s : Arrays.asList(ids.split(","))) {
            String t = s == null ? null : s.trim();
            if (t != null && !t.isEmpty()) {
                out.add(t);
            }
        }
        return out;
    }
}
