package jnpf.culture.util;

import cn.xuyanwu.spring.file.storage.MockMultipartFile;
import jnpf.util.RedisUtil;
import jnpf.util.UserProvider;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 图片组合工具类
 *
 * @author yanwenfu
 * @create 2025-12-26
 */
@Slf4j
@Component
public class ImageComboUtil {

    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private RedissonClient redissonClient;

    public final int MAX_ROUND_SIZE = 33;
    public final String COMBO_POOL_KEY = "culture:combo:pool:";
    public final String PIC_USED_KEY = "culture:pic:used:";
    public final String TEXT_USED_KEY = "culture:text:used:";
    public final String TYPE_PIC = "pic";
    public final String TYPE_TEXT = "text";

    public boolean checkNeedGenerate() {
        return redisUtil.getListSize(getRedisKeyWithTenant(COMBO_POOL_KEY)) == 0L;
    }

    public String getRedisKeyWithTenant(String key) {
        return key + UserProvider.getUser().getTenantId();
    }

    public void generateComboPool(List<String> allPicIds, List<String> allTextIds, List<String> delPicIds, List<String> delTextIds) throws Exception {

        if (allPicIds.isEmpty() || allTextIds.isEmpty()) {
            return;
        }
        String lockKey = "combo:pool:generate:lock";
        // 尝试加锁
        RLock lock = redissonClient.getLock(lockKey);
        boolean locked = false;
        try {
            // 显式 leaseTime
            locked = lock.tryLock(5, 30, TimeUnit.SECONDS);
            if (!locked) {
                return;
            }
            // 二次检查：奖池已有数据，直接返回
            if (redisUtil.getListSize(getRedisKeyWithTenant(COMBO_POOL_KEY)) > 0) {
                return;
            }
            // 1. Redis 中已参与过的
            Set<Object> usedPics = redisUtil.getSet(getRedisKeyWithTenant(PIC_USED_KEY));
            Set<Object> usedTexts = redisUtil.getSet(getRedisKeyWithTenant(TEXT_USED_KEY));
            if (usedPics == null) usedPics = new HashSet<>();
            usedPics.removeIf(v -> delPicIds.contains(v.toString()));
            if (usedTexts == null) usedTexts = new HashSet<>();
            usedTexts.removeIf(v -> delTextIds.contains(v.toString()));
            // 2. 本轮参与数量
            int picLimit = Math.min(MAX_ROUND_SIZE, allPicIds.size());
            int textLimit = Math.min(MAX_ROUND_SIZE, allTextIds.size());
            // 3. 选参与素材
            List<String> roundPics = pickIds(TYPE_PIC, allPicIds, usedPics, picLimit);
            List<String> roundTexts = pickIds(TYPE_TEXT, allTextIds, usedTexts, textLimit);
            // 4. 生成 combo
            List<String> combos = new ArrayList<>(roundPics.size() * roundTexts.size());
            for (String picId : roundPics) {
                for (String textId : roundTexts) {
                    combos.add(picId + "_" + textId);
                }
            }
            Collections.shuffle(combos);
            // 5. 写 Redis（先清再写，防并发残留）
            redisUtil.remove(getRedisKeyWithTenant(COMBO_POOL_KEY));
            redisUtil.insert(getRedisKeyWithTenant(COMBO_POOL_KEY), combos);
            // 6. 标记“参与过生成”
            redisUtil.insert(getRedisKeyWithTenant(PIC_USED_KEY), new HashSet<>(roundPics));
            redisUtil.insert(getRedisKeyWithTenant(TEXT_USED_KEY), new HashSet<>(roundTexts));
        } finally {
            if (locked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    /**
     * 获取指定数量的id(未使用优先)
     * @param pickType 类型(图片, 文案)
     * @param allIds 所有的id
     * @param usedIds 使用过的id
     * @param limit 最大取值
     * @return java.util.List<java.lang.String>
     */
    private List<String> pickIds(String pickType, List<String> allIds, Set<Object> usedIds, int limit) {

        if (allIds.isEmpty() || limit <= 0) {
            return new ArrayList<>();
        }
        Set<String> usedIdSet = usedIds.stream().map(String::valueOf).collect(Collectors.toSet());
        // unused = allIds - usedIds
        List<String> unused = allIds.stream().filter(id -> !usedIdSet.contains(id)).collect(Collectors.toList());
        Collections.shuffle(unused);
        // 用 LinkedHashSet 保证唯一和顺序
        Set<String> resultSet = new LinkedHashSet<>();
        // 优先添加 unused
        for (String id : unused) {
            resultSet.add(id);
            if (resultSet.size() >= limit) {
                break;
            }
        }
        // 够，用 usedIds 补充
        if (resultSet.size() < limit && !usedIdSet.isEmpty()) {
            for (String id : usedIdSet) {
                // LinkedHashSet 自动去重
                resultSet.add(id);
                if (resultSet.size() >= limit) {
                    break;
                }
            }
        }
        if (unused.size() < limit) {
            redisUtil.remove(TYPE_PIC.equals(pickType) ? getRedisKeyWithTenant(PIC_USED_KEY) : getRedisKeyWithTenant(TEXT_USED_KEY));
        }
        // 转回 List 返回
        List<String> finalList = new ArrayList<>(resultSet);
        Collections.shuffle(finalList);
        return finalList;
    }

    public String getRandomCombo(String lastCombo, String userId, List<String> delPicIds, List<String> delTextIds) {

        String todayKey = "culture:combo:" + userId + "_used:" + LocalDate.now();
        String secondBest = null;
        String fallback = null;
        for (int i = 0; i < 3; i++) {
            Object obj = redisUtil.lPop(getRedisKeyWithTenant(COMBO_POOL_KEY));
            if (obj == null) {
                break;
            }
            String combo = obj.toString();
            // 判断combo中图片或文字有没有被删除
            if (!checkComboCouldUse(combo, delPicIds, delTextIds)) {
                i -= 1;
                continue;
            }
            // 最优
            if (!combo.equals(lastCombo) && !redisUtil.sIsMember(todayKey, combo)) {
                return combo;
            }
            // 次优：今天用过，但不等于 last
            if (!combo.equals(lastCombo) && secondBest == null) {
                secondBest = combo;
            }
            // 兜底
            if (fallback == null) {
                fallback = combo;
            }
            redisUtil.rPush(getRedisKeyWithTenant(COMBO_POOL_KEY), combo);
        }
        if (secondBest != null) {
            return secondBest;
        }
        return fallback;
    }

    private boolean checkComboCouldUse(String combo, List<String> delPicIds, List<String> delTextIds) {

        int idx = combo.indexOf('_');
        if (idx <= 0 || idx >= combo.length() - 1) {
            // 格式非法：没有 _ 或在首尾
            return false;
        }
        String picId = combo.substring(0, idx);
        String contentId = combo.substring(idx + 1);
        // 命中已删除 pic
        if (delPicIds != null && delPicIds.contains(picId)) {
            return false;
        }
        // 命中已删除 text
        if (delTextIds != null && delTextIds.contains(contentId)) {
            return false;
        }
        return true;
    }

    /**
     * 生成基础图片
     * @param picUrl 背景图
     * @param companyIconUrl 企业logo
     * @param title 标题
     * @param content 内容
     * @return java.awt.image.BufferedImage
     */
    public BufferedImage generateBaseImage(String picUrl, String companyIconUrl, String title, String content) throws Exception {

        final int CANVAS_WIDTH = 900;
        final int CANVAS_HEIGHT = 1600;
        // 读取背景图
        log.error("图片地址：{}", picUrl);
        BufferedImage bgImage = ImageIO.read(new URL(picUrl));
        // 固定画布 900 * 1600
        BufferedImage combined = new BufferedImage(CANVAS_WIDTH, CANVAS_HEIGHT, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = combined.createGraphics();
        // 抗锯齿
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        // 计算 cover 缩放比例
        double scale = Math.max(
                (double) CANVAS_WIDTH / bgImage.getWidth(),
                (double) CANVAS_HEIGHT / bgImage.getHeight()
        );
        int drawWidth = (int) Math.round(bgImage.getWidth() * scale);
        int drawHeight = (int) Math.round(bgImage.getHeight() * scale);
        // 居中裁剪
        int x = (CANVAS_WIDTH - drawWidth) / 2;
        int y = (CANVAS_HEIGHT - drawHeight) / 2;
        // 画背景（铺满）
        g2d.drawImage(bgImage, x, y, drawWidth, drawHeight, null);
        // 画黑色透明遮罩 50%透明度
        Composite oldComposite = g2d.getComposite();
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
        g2d.setColor(Color.BLACK);
        g2d.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
        // 恢复原始透明度
        g2d.setComposite(oldComposite);
        int margin = 40;
        int marginY = 48;
        // 绘制企业 logo
        if (companyIconUrl != null && !companyIconUrl.isEmpty()) {
            BufferedImage logo = ImageIO.read(new URL(companyIconUrl));
            int logoHeight = 120;
            int logoWidth = logo.getWidth() * logoHeight / logo.getHeight();
            g2d.drawImage(logo, margin, marginY, logoWidth, logoHeight, null);
        }
        // 绘制标题
        int titleY = 250;
        g2d.setColor(Color.WHITE);
        g2d.setFont(FontUtil.loadFont("/fonts/MiSans-Semibold.ttf", 50f));
        int titleEndY = drawStringMultiLine(g2d, title, margin, titleY, CANVAS_WIDTH - 2 * margin, 60, 5);
        // 分割线
        int lineY = titleEndY + 40;
        g2d.fillRect(margin, lineY, CANVAS_WIDTH - 2 * margin, 5);
        // 内容
        int contentY = lineY + 60;
        g2d.setFont(FontUtil.loadFont("/fonts/MiSans-Normal.ttf", 35f));
        drawStringMultiLine(g2d, content, margin, contentY, CANVAS_WIDTH - 2 * margin, 45, 2);
        g2d.dispose();
        return combined;
    }

    /**
     * 绘制多行文本，自动换行
     */
    private int drawStringMultiLine(Graphics2D g2d, String text, int x, int y, int maxWidth, int lineHeight, int charSpacing) {
        FontMetrics fm = g2d.getFontMetrics();
        int curX = x;
        int curY = y;
        for (int i = 0; i < text.length(); i++) {
            String ch = String.valueOf(text.charAt(i));
            int charWidth = fm.stringWidth(ch);
            // 自动换行
            if (curX + charWidth > x + maxWidth) {
                curX = x;
                curY += lineHeight;
            }
            g2d.drawString(ch, curX, curY);
            curX += charWidth + charSpacing;
        }
        return curY;
    }

    /**
     * 生成最终图片
     * @param tempUrl 基础图片
     * @param userIconUrl 用户头像
     * @param userName 用户名称
     * @param days 累计签到天数
     * @return java.awt.image.BufferedImage
     */
    public BufferedImage generateFinalImage(String tempUrl, String userIconUrl, String userName, String days) throws Exception {

        // 读取基础图片
        BufferedImage baseImage = ImageIO.read(new URL(tempUrl));
        int width = baseImage.getWidth();
        int height = baseImage.getHeight();
        BufferedImage combined = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = combined.createGraphics();
        // 高质量绘制
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        // 先画基础图
        g2d.drawImage(baseImage, 0, 0, null);
        // ===== 下半部分布局参数 =====
        int avatarSize = 120;
        // 圆角程度（方形带点圆角）
        int avatarRadius = 32;
        int marginLeft = 40;
        int footerTopY = height - 160;
        // ===== 用户头像（圆角裁剪）=====
        if (userIconUrl != null && !userIconUrl.isEmpty()) {
            BufferedImage avatar = ImageIO.read(new URL(userIconUrl));
            BufferedImage avatarClip = new BufferedImage(avatarSize, avatarSize, BufferedImage.TYPE_INT_ARGB);
            Graphics2D ag = avatarClip.createGraphics();
            ag.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            // ===== 1px 白色边框 =====
            ag.setColor(Color.WHITE);
            ag.setStroke(new BasicStroke(1f));
            RoundRectangle2D border = new RoundRectangle2D.Float(
                    0.5f, 0.5f,
                    avatarSize - 1f, avatarSize - 1f,
                    avatarRadius, avatarRadius
            );
            ag.draw(border);
            // ===== 裁剪区域（向内缩 1px，避免盖住边框）=====
            RoundRectangle2D clip = new RoundRectangle2D.Float(
                    1f, 1f,
                    avatarSize - 2f, avatarSize - 2f,
                    avatarRadius - 1f, avatarRadius - 1f
            );
            ag.setClip(clip);
            // cover 填充头像
            double scale = Math.max(
                    (double) (avatarSize - 2) / avatar.getWidth(),
                    (double) (avatarSize - 2) / avatar.getHeight()
            );
            int drawW = (int) (avatar.getWidth() * scale);
            int drawH = (int) (avatar.getHeight() * scale);
            int dx = (avatarSize - drawW) / 2;
            int dy = (avatarSize - drawH) / 2;
            ag.drawImage(avatar, dx, dy, drawW, drawH, null);
            ag.dispose();
            g2d.drawImage(avatarClip, marginLeft, footerTopY, null);
        }
        // ===== 用户名称 =====
        int textStartX = marginLeft + avatarSize + 30;
        int nameY = footerTopY + 45;
        g2d.setColor(Color.WHITE);
        g2d.setFont(FontUtil.loadFont("/fonts/MiSans-Semibold.ttf", 36f));
        g2d.drawString(userName, textStartX, nameY);
        // ===== 累计签到天数 =====
        int countY = nameY + 70;
        String prefix = "已累计打卡";
        String suffix = "天";
        // 前缀
        g2d.setFont(FontUtil.loadFont("/fonts/MiSans-Normal.ttf", 30f));
        FontMetrics fmPrefix = g2d.getFontMetrics();
        g2d.drawString(prefix, textStartX, countY);
        int xOffset = textStartX + fmPrefix.stringWidth(prefix) + 6;
        // 数字（放大）
        g2d.setFont(FontUtil.loadFont("/fonts/MiSans-Semibold.ttf", 48f));
        FontMetrics fmDays = g2d.getFontMetrics();
        g2d.drawString(days, xOffset, countY);
        xOffset += fmDays.stringWidth(days) + 6;
        // 后缀
        g2d.setFont(FontUtil.loadFont("/fonts/MiSans-Normal.ttf", 30f));
        g2d.drawString(suffix, xOffset, countY);
        countY -= 10;
        // 时间图戳
        BufferedImage bottomIcon;
        try (InputStream is = getClass()
                .getClassLoader()
                .getResourceAsStream("img/icon-bottom.png")) {
            assert is != null;
            bottomIcon = ImageIO.read(is);
        }
        // 缩放
        int newWidth = bottomIcon.getWidth() / 10 * 7;
        int newHeight = bottomIcon.getHeight() / 10 * 7;
        Image scaledIcon = bottomIcon.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH);
        // 计算绘制位置
        int x = width - newWidth;
        int y = height - newHeight;
        // 绘制缩放后的图
        g2d.drawImage(scaledIcon, x, y, null);

        LocalDateTime now = LocalDateTime.now();
        int year = now.getYear();
        int month = now.getMonth().getValue();
        int day = now.getDayOfMonth();
        int hour = now.getHour();
        int minute = now.getMinute();
        String topLText = String.format("%02d", day);
        String topRText = "日";
        String midText = year + "/" + String.format("%02d", month);
        String bottomText = String.format("%02d", hour) + ":" + String.format("%02d", minute);

        g2d.setFont(FontUtil.loadFont("/fonts/MiSans-Semibold.ttf", 66f));
        g2d.setColor(Color.WHITE);
        int textX = width - 252;
        countY = countY - 125;
        g2d.drawString(topLText, textX, countY);

        g2d.setFont(FontUtil.loadFont("/fonts/MiSans-Normal.ttf", 34f));
        g2d.setColor(new Color(255, 255, 255, 180));
        textX = textX + 90;
        g2d.drawString(topRText, textX, countY);

        g2d.setFont(FontUtil.loadFont("/fonts/MiSans-Normal.ttf", 38f));
        g2d.setColor(Color.WHITE);
        textX = textX - 103;
        countY = countY + 55;
        g2d.drawString(midText, textX, countY);

        g2d.setFont(FontUtil.loadFont("/fonts/MiSans-Normal.ttf", 34f));
        g2d.setColor(new Color(255, 255, 255, 180));
        textX = textX + 28;
        countY = countY + 55;
        g2d.drawString(bottomText, textX, countY);
        g2d.dispose();
        return combined;
    }

    public MultipartFile bufferedImageToMultipartFile(BufferedImage image) throws Exception {

        String suffix = "png";
        String fileName = UUID.randomUUID() + "." + suffix;
        // 自动关闭流
        try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
            ImageIO.write(image, suffix, os);
            byte[] bytes = os.toByteArray();
            return new MockMultipartFile(fileName, fileName, "image/" + suffix, bytes);
        }
    }
}
