"""
文本处理引擎 — Spintax + 变量替换 + 魔术编码 + 内容混淆
"""
import re
import random
import html
import unicodedata
import hashlib
import time
from typing import List, Optional

from core.models import Email, Variable


class SpintaxProcessor:
    """Spintax {a|b|c} 旋转语法"""

    @staticmethod
    def process(text: str, rng: random.Random = None) -> str:
        if rng is None:
            rng = random.Random()
        if '{' not in text:
            return text
        result = []
        i = 0
        depth = 0
        start = -1
        while i < len(text):
            if text[i] == '{':
                if depth == 0:
                    start = i
                depth += 1
            elif text[i] == '}':
                depth -= 1
                if depth == 0 and start >= 0:
                    block = text[start + 1:i]
                    options = SpintaxProcessor._split_options(block)
                    if options:
                        chosen = rng.choice(options)
                        result.append(SpintaxProcessor.process(chosen, rng))
                    start = -1
            elif depth == 0:
                result.append(text[i])
            i += 1
        if depth > 0 and start >= 0:
            result.append(text[start:])
        return ''.join(result)

    @staticmethod
    def _split_options(block: str) -> List[str]:
        options = []
        current = []
        depth = 0
        for ch in block:
            if ch == '{':
                depth += 1
                current.append(ch)
            elif ch == '}':
                depth -= 1
                current.append(ch)
            elif ch == '|' and depth == 0:
                options.append(''.join(current))
                current = []
            else:
                current.append(ch)
        options.append(''.join(current))
        return [o for o in options if o]


class VariableProcessor:
    """变量替换 [变量名]"""

    def __init__(self, variables: List[Variable] = None):
        self.variables = {}
        if variables:
            for v in variables:
                self.variables[v.var_name] = v
        self.rng = random.Random()

    def replace(self, text: str, email: Email = None) -> str:
        if not text:
            return text

        def replace_var(match):
            var_name = match.group(1)
            if var_name in self.variables:
                var = self.variables[var_name]
                with var.locker:
                    if var.var_content:
                        if var.is_seq:
                            idx = var.use_times % len(var.var_content)
                            var.use_times += 1
                            return var.var_content[idx]
                        else:
                            return self.rng.choice(var.var_content)
            return match.group(0)

        text = re.sub(r'\[([^\]]+)\]', replace_var, text)

        if email:
            text = text.replace('[email]', email.email_address)
            text = text.replace('[name]', email.name or '')
            text = text.replace('[var]', email.var_text or '')

        text = text.replace('[random]', str(self.rng.randint(10000, 99999)))
        text = text.replace('[timestamp]', str(int(time.time())))
        text = text.replace('[date]', time.strftime('%Y-%m-%d'))
        text = text.replace('[time]', time.strftime('%H:%M:%S'))

        return text


class MagicEncoder:
    """魔术编码/内容混淆引擎 — 移植BerlloMail RandomText.cs MagicNum系列"""

    ZERO_CHARS = ['\u200b', '\u200c', '\u200d', '\ufeff']

    # ToNum1-7: 数字→Unicode数字符号映射(7套)
    NUM_MAPS = {
        1: str.maketrans('0123456789', '①②③④⑤⑥⑦⑧⑨⑩'),
        2: str.maketrans('0123456789', '⒈⒉⒊⒋⒌⒍⒎⒏⒐⒑'),
        3: str.maketrans('0123456789', '⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽'),
        4: str.maketrans('0123456789', '㈠㈡㈢㈣㈤㈥㈦㈧㈨㈩'),
        5: str.maketrans('0123456789', '零一二三四五六七八九'),
        6: str.maketrans('0123456789', '❶❷❸❹❺❻❼❽❾❿'),
        7: str.maketrans('0123456789', '㊀㊁㊂㊃㊄㊅㊆㊇㊈㊉'),
    }

    MAGIC_CODES = [
        '\u200b', '\u200c', '\u200d', '\ufeff', '\u2060', '\u00ad',
        '\u180e', '\u200e', '\u200f', '\u2061', '\u2062', '\u2063',
    ]

    # 大字库 — 移植BerlloMail RandomText字库
    _CN_CHARS = '的一是不了人我在有他这中大为上来个国地到以说时要就出会可也你对生能而子那得于着下自之年过发后作里用道行所然家种事成方多经么去法学如都同当现想没动面起看定十分主前两然它把其第与已被实做次个就里间外那起看定十分主前两然它把其第与已被实做次个就里间外那起看定十分主前两然它把其第与已被实做次个就里间外之上下来去到从自前后左右上下东西南北里外前后高低长短大小多少新旧好坏快慢前后左右上下东西南北里外前后高低长短大小多少新旧好坏快慢春夏秋冬冷热干湿明暗日夜晨昏黑白红黄蓝绿紫灰粉色天地山水风云雨雪雷电日月星辰花草树木鱼鸟虫兽人身手足头面口鼻耳目手足头面口鼻耳目手足头面口鼻耳目父母子女兄弟姐妹家庭邻居朋友同事师生国王将军士兵工农学商文武强弱胜败生死喜怒哀乐爱恨恐惧惊悲笑哭叫喊唱歌跳舞吃饭穿衣住行步行跑步飞跃旋转开关闭合推拉提放买卖租赁赠送接受拒绝同意反对赞成支持帮助保护破坏建设拆除开始结束继续停止暂停恢复完成失败成功错误正确真假善恶美丑好坏快慢轻重冷热干湿明暗日夜晨昏'
    _JP_CHARS = 'あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをんABCDEFGHIJKLMNOPQRSTUVWXYZあいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをんアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン一二三四五六七八九十百千万億兆今日昨日明日今週先週来週今月先月来月今年去年来年朝昼夜半午前午後上野下左前右後東西南北南北左右上下世界日本中国韓国美国英国独仏伊加豪露上海北京東京大阪名古屋京都横浜福岡札幌仙台高関広'
    _KR_CHARS = '가나다라마바사아자차카타파하거너더러머버서어저처커터퍼허기니디리미비시이지치키티피히까나따라말바사아자찌카타파하갸냐댜랴먀뱌샤샤쟈챠캬탸퍄햐게네데레메베세에제체케테페헤개내대래매배새애재챼캬태패해교뇨됴료묘뵤쇼요졸쵸꼬뇨떼료뮤뷰슈유쥬츄큐튜퓨휴간난단란만반산안잔찬칸탄판한갈날달랄말발산알잔찰칼탈팔할'
    _RU_CHARS = 'абвгдежзийклмнопрстуфхцчшщъыьэюяАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ'
    _FR_CHARS = 'abcdefghijklmnopqrstuvwxyzéèêëàâçùûüôîï'
    _GR_CHARS = 'αβγδεζηθικλμνξοπρστυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ'
    _AR_CHARS = 'ابتثجحخدذرزسشصضطظعغفقكلمنهوي'
    _TH_CHARS = 'กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรลวศษสหฬอฮ'
    _MARKS = '，。！？：；''""（）【】《》——…、·‰℃℉№☆★○●◎◇◆□■△▲※→←↑↓〓§⊕⊗∈∏∑√∝∞∽≌≠≤≥≈≠≡∫∮⊥∥∧∨℃°′″〒◯□△◇＼／＆％＃＠＊＋－＝＜＞（）［］｛｝：；，。／？｜＼＇＂'
    _EMOJIS = '😀😃😄😁😆😅🤣😂🙂🙃😉😊😇🥰😍🤩😘😗😚😙🥲😋😛😜🤪😝🤑🤗🤭🤫🤔🤐🤨😐😑😶😏😒🙄😬😤😪😴😌😛🤤🐶🐱🐭🐹🐰🦊🐻🐼🐨🐯🦁🐮🐷🐰🦊🐻🐨🐯🦁🐮🐷🐰🦊🐻🐨🐯🦁🐮🐷🐰🦊🐻🐨🍎🍊🍌🍉🍇🍓🍒🍑🥭🍍🥥🥝🍅🍆🥑🥦🌽🥕🥔🍠🥐🍞🧀🥚🍳🥞🥓🥩🍗🍖🌭🍔🍟🍕🥪🌮🌯🥗🥘🍜🍱🍣🍙🍘🍚🍢🍡🍧🍨🍦🍰🎂🍮🍭'

    def __init__(self, config: dict = None):
        self.config = config or {}
        self.rng = random.Random()

    def _extract_regions(self, text: str) -> list:
        """提取[start]...[end]标记区域"""
        regions = []
        pattern = re.compile(r'\[start\](.*?)\[/start\]', re.DOTALL)
        for m in pattern.finditer(text):
            regions.append((m.start(), m.end(), m.group(1)))
        return regions

    def _process_regions(self, text: str, transform_func) -> str:
        """对标记区域内文本执行转换，区域外保持不变"""
        def replace_region(match):
            return f'[start]{transform_func(match.group(1))}[/start]'
        return re.sub(r'\[start\](.*?)\[/start\]', replace_region, text, flags=re.DOTALL)

    # HTML 保护: 标签名/属性名/URL/实体 内不做任何变换 (否则破坏结构)
    # 占位符用 Unicode 私有区 (PUA-A): 各变换只处理 ASCII字母/数字/中日文字, 不会碰它
    _HTML_TAG_RE = re.compile(r'<[^>]*>|https?://[^\s<>"\']+|&[#\w]+;')
    _PH_OPEN = '\ue000'
    _PH_CLOSE = '\ue001'
    _PH_RE = re.compile('\ue000(\\d+)\ue001')

    def _protect_html(self, text: str) -> tuple:
        """切分 HTML: 标签/URL/实体 → PUA 占位符, 变换只作用于文本节点"""
        parts = []

        def _stash(m):
            parts.append(m.group(0))
            return self._PH_OPEN + str(len(parts) - 1) + self._PH_CLOSE

        return re.sub(self._HTML_TAG_RE, _stash, text), parts

    def _restore_html(self, text: str, parts: list) -> str:
        if not parts:
            return text
        return self._PH_RE.sub(lambda m: parts[int(m.group(1))], text)

    def process(self, text: str) -> str:
        if not text or not self.config.get('enabled', False):
            return text

        # ★ HTML 保护: 先把 标签/URL/实体 摘出去, 变换只作用于文本节点, 最后还原
        _is_html = '<' in text and '>' in text
        if not _is_html:
            return self._process_inner(text)
        protected, parts = self._protect_html(text)
        return self._restore_html(self._process_inner(protected), parts)

    def _process_inner(self, text: str) -> str:
        if not text:
            return text

        level = self.config.get('level', 'mid')

        if self.config.get('html_entity', False):
            text = self._magic_dec(text)
        if self.config.get('hex_encode', False):
            text = self._magic_hex(text)
        if self.config.get('zero_char', False):
            text = self._insert_zero_char(text)
        if self.config.get('magic_code', False):
            text = self._insert_magic_code(text)
        if self.config.get('html_tag', False):
            text = self._insert_html_tag(text)
        if self.config.get('wide_char', False):
            text = self._to_wide(text)
        if self.config.get('full_half', False):
            text = self._switch_full_half(text)
        if self.config.get('magic_num', False):
            text = self._magic_num(text)

        if level in ('mid', 'high'):
            dilution = []
            if self.config.get('random_cn', False):
                dilution.append(self._random_chinese)
            if self.config.get('random_jp', False):
                dilution.append(self._random_japanese)
            if self.config.get('random_kr', False):
                dilution.append(self._random_korean)
            if self.config.get('random_ru', False):
                dilution.append(self._random_russian)
            if self.config.get('random_fr', False):
                dilution.append(self._random_french)
            if self.config.get('random_gr', False):
                dilution.append(self._random_greek)
            if self.config.get('random_ar', False):
                dilution.append(self._random_arabic)
            if self.config.get('random_th', False):
                dilution.append(self._random_thai)
            if self.config.get('random_en', False):
                dilution.append(self._random_english)
            if self.config.get('random_emoji', False):
                dilution.append(self._random_emoji)
            if self.config.get('random_mark', False):
                dilution.append(self._random_marks)
            if self.config.get('random_css', False):
                dilution.append(self._random_css)
            if dilution:
                text = self._dilute(text, dilution, level)

        return text

    def _magic_num(self, text: str) -> str:
        """MagicNum: [start]...[end]区域内数字→Unicode数字符号(随机选1-7套)"""
        def transform(content):
            map_id = self.rng.randint(1, 7)
            trans = self.NUM_MAPS[map_id]
            return content.translate(trans)
        return self._process_regions(text, transform)

    def _iter_text_chars(self, text: str):
        """逐字符生成器: PUA 占位符段(\ue000N\ue001)整段原样产出, 不参与变换"""
        buf = []
        in_ph = False
        for ch in text:
            if ch == self._PH_OPEN:
                in_ph = True
                buf.append(ch)
                continue
            if ch == self._PH_CLOSE:
                buf.append(ch)
                yield ''.join(buf)
                buf = []
                in_ph = False
                continue
            if in_ph:
                buf.append(ch)
            else:
                yield ch

    def _magic_dec(self, text: str) -> str:
        """MagicDec: 字母→十进制HTML实体 &#NNN; (PUA占位符豁免)"""
        out = []
        for piece in self._iter_text_chars(text):
            if len(piece) == 1 and piece.isalpha() and self.rng.random() < 0.3:
                out.append(f'&#{ord(piece)};')
            else:
                out.append(piece)
        return ''.join(out)

    def _magic_hex(self, text: str) -> str:
        """MagicHex: 字母→十六进制HTML实体 &#xHH; (PUA占位符豁免)"""
        out = []
        for piece in self._iter_text_chars(text):
            if len(piece) == 1 and piece.isalpha() and self.rng.random() < 0.2:
                out.append(f'&#x{ord(piece):x};')
            else:
                out.append(piece)
        return ''.join(out)

    def _insert_zero_char(self, text: str) -> str:
        result = []
        for piece in self._iter_text_chars(text):
            result.append(piece)
            if len(piece) == 1 and piece.isalpha() and self.rng.random() < 0.15:
                result.append(self.rng.choice(self.ZERO_CHARS))
        return ''.join(result)

    def _insert_magic_code(self, text: str) -> str:
        result = []
        for piece in self._iter_text_chars(text):
            result.append(piece)
            if len(piece) == 1 and self.rng.random() < 0.08:
                result.append(self.rng.choice(self.MAGIC_CODES))
        return ''.join(result)

    def _insert_html_tag(self, text: str) -> str:
        tags = ['<span></span>', '<div></div>', '<b></b>', '<i></i>',
                '<span style="display:none">x</span>']
        parts = text.split(' ')
        result = []
        for i, part in enumerate(parts):
            result.append(part)
            if self.rng.random() < 0.1:
                result.append(self.rng.choice(tags))
            if i < len(parts) - 1:
                result.append(' ')
        return ''.join(result)

    def _to_wide(self, text: str) -> str:
        result = []
        for piece in self._iter_text_chars(text):
            if len(piece) == 1 and piece.isascii() and piece.isalpha() and self.rng.random() < 0.2:
                try:
                    result.append(chr(ord(piece) + 0xFEE0))
                except Exception:
                    result.append(piece)
            else:
                result.append(piece)
        return ''.join(result)

    def _switch_full_half(self, text: str) -> str:
        result = []
        for piece in self._iter_text_chars(text):
            if len(piece) == 1 and piece.isascii() and self.rng.random() < 0.15:
                try:
                    result.append(chr(ord(piece) + 0xFEE0))
                except Exception:
                    result.append(piece)
            else:
                result.append(piece)
        return ''.join(result)

    def _dilute(self, text: str, generators: list, level: str) -> str:
        count = 1 if level == 'mid' else 3
        words = text.split(' ')
        result = []
        for word in words:
            result.append(word)
            if self.rng.random() < 0.05:
                gen = self.rng.choice(generators)
                result.append(gen(self.rng, count))
        return ' '.join(result)

    @staticmethod
    def _random_chinese(rng, count=1) -> str:
        return ''.join(rng.choice(MagicEncoder._CN_CHARS) for _ in range(count))

    @staticmethod
    def _random_japanese(rng, count=1) -> str:
        return ''.join(rng.choice(MagicEncoder._JP_CHARS) for _ in range(count))

    @staticmethod
    def _random_korean(rng, count=1) -> str:
        return ''.join(rng.choice(MagicEncoder._KR_CHARS) for _ in range(count))

    @staticmethod
    def _random_russian(rng, count=1) -> str:
        return ''.join(rng.choice(MagicEncoder._RU_CHARS) for _ in range(count))

    @staticmethod
    def _random_french(rng, count=1) -> str:
        return ''.join(rng.choice(MagicEncoder._FR_CHARS) for _ in range(count))

    @staticmethod
    def _random_greek(rng, count=1) -> str:
        return ''.join(rng.choice(MagicEncoder._GR_CHARS) for _ in range(count))

    @staticmethod
    def _random_arabic(rng, count=1) -> str:
        return ''.join(rng.choice(MagicEncoder._AR_CHARS) for _ in range(count))

    @staticmethod
    def _random_thai(rng, count=1) -> str:
        return ''.join(rng.choice(MagicEncoder._TH_CHARS) for _ in range(count))

    @staticmethod
    def _random_marks(rng, count=1) -> str:
        return ''.join(rng.choice(MagicEncoder._MARKS) for _ in range(count))

    @staticmethod
    def _random_english(rng, count=1) -> str:
        return ''.join(rng.choice('abcdefghijklmnopqrstuvwxyz') for _ in range(count))

    @staticmethod
    def _random_emoji(rng, count=1) -> str:
        return ''.join(rng.choice(MagicEncoder._EMOJIS) for _ in range(count))

    @staticmethod
    def _random_css(rng, count=1) -> str:
        props = ['margin:0', 'padding:0', 'border:0', 'color:inherit', 'font-size:0', 'display:none',
                 'line-height:0', 'max-width:0', 'min-height:0', 'overflow:hidden', 'visibility:hidden',
                 'position:static', 'clear:both', 'zoom:1', 'opacity:0', 'border-collapse:collapse']
        return f'<span style="{rng.choice(props)}"></span>'


class TextToImage:
    """文字转图片"""

    @staticmethod
    def generate(text: str, font_size: int = 14, bg_color: str = '#ffffff',
                 font_name: str = '微软雅黑', base64_embed: bool = True,
                 watermark: bool = False, interference: bool = False) -> str:
        try:
            from PIL import Image, ImageDraw, ImageFont
            import io
            import base64
        except ImportError:
            return text

        font = None
        try:
            font = ImageFont.truetype(font_name, font_size)
        except Exception:
            font = ImageFont.load_default()

        dummy = ImageDraw.Draw(Image.new('RGB', (1, 1)))
        bbox = dummy.textbbox((0, 0), text, font=font)
        w = bbox[2] - bbox[0] + 10
        h = bbox[3] - bbox[1] + 10

        img = Image.new('RGB', (w, h), bg_color)
        draw = ImageDraw.Draw(img)

        draw.text((5, 2), text, fill='#333333', font=font)

        if interference:
            for _ in range(3):
                x1 = random.randint(0, w)
                y1 = random.randint(0, h)
                x2 = random.randint(0, w)
                y2 = random.randint(0, h)
                draw.line([(x1, y1), (x2, y2)], fill='#cccccc', width=1)

        if watermark:
            draw.text((w - 50, h - 15), '©', fill='#999999', font=font)

        buf = io.BytesIO()
        img.save(buf, format='PNG')
        img_data = buf.getvalue()

        if base64_embed:
            b64 = base64.b64encode(img_data).decode('ascii')
            return f'<img src="data:image/png;base64,{b64}" alt=""/>'
        else:
            return f'<img src="cid:text_img" alt=""/>'