package com.writeoff.security;

import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.time.Instant;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 图形验证码服务 —— 基于 Java 2D 生成 4 位字母数字混合验证码。
 * 存储于内存 ConcurrentHashMap，单实例场景使用。
 */
@Service
public class CaptchaService {

    private static final int CODE_LENGTH = 4;
    private static final int WIDTH = 130;
    private static final int HEIGHT = 40;
    private static final long EXPIRE_SECONDS = 5 * 60; // 5 分钟
    private static final String CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";

    private final ConcurrentHashMap<String, CaptchaRecord> store = new ConcurrentHashMap<>();

    /**
     * 生成验证码。
     *
     * @return map 包含 captchaId 和 image (Base64 PNG)
     */
    public Map<String, String> generate() {
        cleanExpired();
        String captchaId = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
        Random random = new Random();
        StringBuilder code = new StringBuilder();
        for (int i = 0; i < CODE_LENGTH; i++) {
            code.append(CHARS.charAt(random.nextInt(CHARS.length())));
        }
        String codeStr = code.toString();
        store.put(captchaId, new CaptchaRecord(codeStr, Instant.now()));

        String base64 = renderImage(codeStr, random);

        Map<String, String> result = new LinkedHashMap<>();
        result.put("captchaId", captchaId);
        result.put("image", "data:image/png;base64," + base64);
        return result;
    }

    /**
     * 校验验证码（一次性，校验后即删除）。
     */
    public boolean verify(String captchaId, String code) {
        if (captchaId == null || code == null) {
            return false;
        }
        CaptchaRecord record = store.remove(captchaId);
        if (record == null) {
            return false;
        }
        if (record.isExpired()) {
            return false;
        }
        return record.code.equalsIgnoreCase(code.trim());
    }

    private String renderImage(String code, Random random) {
        BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = image.createGraphics();

        // 启用抗锯齿
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        // 背景
        g.setColor(new Color(240, 243, 248));
        g.fillRect(0, 0, WIDTH, HEIGHT);

        // 干扰线
        for (int i = 0; i < 5; i++) {
            g.setColor(new Color(180 + random.nextInt(50), 180 + random.nextInt(50), 200 + random.nextInt(50)));
            g.setStroke(new BasicStroke(1.2f));
            g.drawLine(random.nextInt(WIDTH), random.nextInt(HEIGHT), random.nextInt(WIDTH), random.nextInt(HEIGHT));
        }

        // 干扰点
        for (int i = 0; i < 30; i++) {
            g.setColor(new Color(150 + random.nextInt(80), 150 + random.nextInt(80), 180 + random.nextInt(60)));
            g.fillOval(random.nextInt(WIDTH), random.nextInt(HEIGHT), 2, 2);
        }

        // 绘制字符
        Color[] charColors = {
                new Color(59, 130, 246),
                new Color(99, 102, 241),
                new Color(139, 92, 246),
                new Color(14, 165, 233),
        };
        g.setFont(new Font("SansSerif", Font.BOLD, 26));
        int charSpacing = (WIDTH - 20) / CODE_LENGTH;
        for (int i = 0; i < code.length(); i++) {
            g.setColor(charColors[i % charColors.length]);
            double angle = (random.nextDouble() - 0.5) * 0.4;
            int x = 12 + i * charSpacing;
            int y = 28 + random.nextInt(6);
            g.rotate(angle, x, y);
            g.drawString(String.valueOf(code.charAt(i)), x, y);
            g.rotate(-angle, x, y);
        }

        g.dispose();

        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ImageIO.write(image, "png", baos);
            return Base64.getEncoder().encodeToString(baos.toByteArray());
        } catch (Exception e) {
            return "";
        }
    }

    private void cleanExpired() {
        store.entrySet().removeIf(entry -> entry.getValue().isExpired());
    }

    private static class CaptchaRecord {
        final String code;
        final Instant createdAt;

        CaptchaRecord(String code, Instant createdAt) {
            this.code = code;
            this.createdAt = createdAt;
        }

        boolean isExpired() {
            return Instant.now().getEpochSecond() - createdAt.getEpochSecond() > EXPIRE_SECONDS;
        }
    }
}
