"""图形验证码 & 滑块拼图验证码"""
import random
import string
import io
import hashlib
import hmac
import time
import base64
from typing import Tuple

from PIL import Image, ImageDraw, ImageFont

# ── HMAC 一次性 token ──────────────────────────────────
_SECRET = hashlib.sha256(b"cma-captcha-secret-2024").digest()

def sign_token(captcha_id: str, value: str) -> str:
    """签发一次性 token"""
    ts = str(int(time.time()))
    msg = f"{captcha_id}:{value}:{ts}".encode()
    sig = hmac.new(_SECRET, msg, "sha256").hexdigest()[:12]
    return f"{captcha_id}.{value}.{ts}.{sig}"


def verify_token(token: str, expected_value: str, max_age: int = 300) -> bool:
    """验证一次性 token，防止重放"""
    try:
        parts = token.split(".")
        if len(parts) != 4:
            return False
        captcha_id, value, ts_str, sig = parts
        if value != expected_value:
            return False
        if int(time.time()) - int(ts_str) > max_age:
            return False
        expected_sig = hmac.new(
            _SECRET, f"{captcha_id}:{value}:{ts_str}".encode(), "sha256"
        ).hexdigest()[:12]
        if sig != expected_sig:
            return False
        return True
    except (ValueError, IndexError):
        return False


# ── 字体 ────────────────────────────────────────────────
def _load_font(size: int) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
    """优先使用中文字体，回退默认"""
    for p in [
        "/usr/share/fonts/opentype/noto/NotoSansCJKSC-VF.otf",
        "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
        "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
        "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
    ]:
        try:
            return ImageFont.truetype(p, size)
        except (IOError, OSError):
            continue
    return ImageFont.load_default()


# ── 图形验证码（4位字母数字） ────────────────────────────
def generate_image_captcha() -> Tuple[str, str, str]:
    """
    返回: (captcha_id, plain_text, base64_png)
    仅需保持 captcha_id 与 plain_text 在 token 中绑定
    """
    chars = string.ascii_uppercase + string.digits
    text = "".join(random.choices(chars, k=4))
    captcha_id = hashlib.md5(f"{time.time()}{random.random()}".encode()).hexdigest()[:16]

    w, h = 160, 60
    img = Image.new("RGB", (w, h), (255, 255, 255))
    draw = ImageDraw.Draw(img)
    font = _load_font(36)

    # 干扰线
    for _ in range(5):
        x1, y1 = random.randint(0, w // 2), random.randint(0, h)
        x2, y2 = random.randint(w // 2, w), random.randint(0, h)
        draw.line(
            [(x1, y1), (x2, y2)],
            fill=(random.randint(100, 200), random.randint(100, 200), random.randint(100, 200)),
            width=2,
        )

    # 噪点
    for _ in range(80):
        draw.point(
            (random.randint(0, w), random.randint(0, h)),
            fill=(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)),
        )

    # 文字
    x_offset = 12
    for ch in text:
        angle = random.randint(-25, 25)
        ch_img = Image.new("RGBA", (36, 48), (255, 255, 255, 0))
        ch_draw = ImageDraw.Draw(ch_img)
        ch_draw.text((2, -2), ch, fill=(random.randint(0, 80), random.randint(0, 80), random.randint(0, 80)), font=font)
        rotated = ch_img.rotate(angle, expand=True, fillcolor=(255, 255, 255, 0))
        img.paste(rotated, (x_offset, random.randint(5, 15)), rotated)
        x_offset += 34

    buf = io.BytesIO()
    img.save(buf, format="PNG")
    b64 = base64.b64encode(buf.getvalue()).decode()

    return captcha_id, text, f"data:image/png;base64,{b64}"


# ── 滑块拼图验证码 ──────────────────────────────────────
def generate_slider_captcha() -> Tuple[str, str, dict]:
    """
    返回: (captcha_id, answer_xxx, {
        bg: base64 背景图,
        slice: base64 滑块拼图块,
        x: 缺口x坐标 (前端拼图用)
    })
    """
    captcha_id = hashlib.md5(f"{time.time()}{random.random()}".encode()).hexdigest()[:16]

    bg_w, bg_h = 280, 160
    img = Image.new("RGB", (bg_w, bg_h), (240, 240, 240))
    draw = ImageDraw.Draw(img)

    # 背景色块
    for _ in range(3):
        r = random.randint(200, 255)
        g = random.randint(200, 255)
        b = random.randint(200, 255)
        x1, y1 = random.randint(0, bg_w - 60), random.randint(0, bg_h - 40)
        draw.rectangle([x1, y1, x1 + 60, y1 + 40], fill=(r, g, b))

    # 随机干扰线
    for _ in range(8):
        draw.line(
            [
                (random.randint(0, bg_w), random.randint(0, bg_h)),
                (random.randint(0, bg_w), random.randint(0, bg_h)),
            ],
            fill=(random.randint(180, 220), random.randint(180, 220), random.randint(180, 220)),
            width=1,
        )

    # 随机绘制文字（增加OCR难度）
    font_small = _load_font(14)
    for _ in range(12):
        x = random.randint(0, bg_w - 30)
        y = random.randint(0, bg_h - 20)
        c = random.choice(string.ascii_uppercase)
        draw.text((x, y), c, fill=(random.randint(150, 220), random.randint(150, 220), random.randint(150, 220)), font=font_small)

    # 缺口位置
    gap_size = 40
    gap_x = random.randint(20, bg_w - gap_size - 20)
    gap_y = random.randint(15, bg_h - gap_size - 15)

    # 在背景图上切出缺口（深色填充）
    draw.rectangle([gap_x, gap_y, gap_x + gap_size, gap_y + gap_size], fill=(80, 80, 80))

    # 创建滑块拼图块（从另一位置裁取）
    slice_x = max(0, gap_x - 80)
    if slice_x + gap_size > bg_w:
        slice_x = bg_w - gap_size - 10
    slice_img = img.crop((slice_x, gap_y, slice_x + gap_size, gap_y + gap_size))

    # 给滑块块加白色边框
    slice_with_border = Image.new("RGB", (gap_size + 4, gap_size + 4), (255, 255, 255))
    slice_with_border.paste(slice_img, (2, 2))

    buf_bg = io.BytesIO()
    img.save(buf_bg, format="PNG")
    bg_b64 = base64.b64encode(buf_bg.getvalue()).decode()

    buf_slice = io.BytesIO()
    slice_with_border.save(buf_slice, format="PNG")
    slice_b64 = base64.b64encode(buf_slice.getvalue()).decode()

    # answer 存 gap_x 的字符串形式
    answer = hashlib.md5(str(gap_x).encode()).hexdigest()[:16]

    return captcha_id, f"ans_{answer}", {
        "bg": f"data:image/png;base64,{bg_b64}",
        "slice": f"data:image/png;base64,{slice_b64}",
        "gap_x": gap_x,
    }
