"""
正则规则服务（重构版 - 文件夹结构）

负责加载、管理和应用正则替换规则。
使用文件夹结构组织规则，兼容 SillyTavern 格式。

文件结构：
data/regex/
├── global/              # 全局规则
│   └── default.json
├── characters/          # 角色卡绑定规则
│   └── {characterName}.json
└── presets/             # 预设绑定规则
    └── {presetName}.json
"""
import re
import json
import logging
from pathlib import Path
from typing import List, Optional, Dict
from uuid import uuid4

from core.config import settings
from models.regex_rules import RegexRule, RegexRuleset, RegexScope, RegexPlacement, SubstituteMode
from services.system_settings_service import system_settings_service

logger = logging.getLogger(__name__)


class RegexService:
    """
    正则替换规则服务（文件夹结构版）
    """
    
    def __init__(self):
        self.regex_base_path = settings.DATA_PATH / "regex"
        self.global_path = self.regex_base_path / "global"
        self.characters_path = self.regex_base_path / "characters"
        self.presets_path = self.regex_base_path / "presets"
        
        # 内存缓存
        self.global_rulesets: Dict[str, RegexRuleset] = {}
        self.character_rulesets: Dict[str, RegexRuleset] = {}  # key: characterName
        self.preset_rulesets: Dict[str, RegexRuleset] = {}     # key: presetName
        
        self._ensure_directories()
        self._load_all_rules()
    
    def _ensure_directories(self):
        """确保目录结构存在"""
        for path in [self.regex_base_path, self.global_path, self.characters_path, self.presets_path]:
            path.mkdir(parents=True, exist_ok=True)
    
    def _load_all_rules(self):
        """加载所有规则"""
        self._load_global_rules()
        self._load_character_rules()
        self._load_preset_rules()
    
    def _load_global_rules(self):
        """加载全局规则"""
        if not self.global_path.exists():
            return
        
        for json_file in self.global_path.glob("*.json"):
            try:
                with open(json_file, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                
                if isinstance(data, list):
                    # SillyTavern 格式
                    ruleset = self._convert_sillytavern_format(data, json_file.stem)
                elif isinstance(data, dict) and 'rules' in data:
                    # 我们的规则集格式
                    ruleset = RegexRuleset(**data)
                else:
                    logger.warning(f"未知的规则文件格式: {json_file}")
                    continue
                
                self.global_rulesets[ruleset.id] = ruleset
                logger.info(f"加载全局规则集: {ruleset.name} ({len(ruleset.rules)} 条规则)")
            except Exception as e:
                logger.error(f"加载全局规则失败 {json_file}: {e}")
    
    def _load_character_rules(self):
        """加载角色卡绑定规则"""
        if not self.characters_path.exists():
            return
        
        for json_file in self.characters_path.glob("*.json"):
            try:
                character_name = json_file.stem
                with open(json_file, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                
                if isinstance(data, list):
                    ruleset = self._convert_sillytavern_format(data, character_name, RegexScope.CHARACTER)
                elif isinstance(data, dict) and 'rules' in data:
                    ruleset = RegexRuleset(**data)
                else:
                    continue
                
                # 确保所有规则的 scope 正确
                for rule in ruleset.rules:
                    rule.scope = RegexScope.CHARACTER
                    rule.characterName = character_name
                
                self.character_rulesets[character_name] = ruleset
                logger.info(f"加载角色规则: {character_name} ({len(ruleset.rules)} 条规则)")
            except Exception as e:
                logger.error(f"加载角色规则失败 {json_file}: {e}")
    
    def _load_preset_rules(self):
        """加载预设绑定规则"""
        if not self.presets_path.exists():
            return
        
        for json_file in self.presets_path.glob("*.json"):
            try:
                preset_name = json_file.stem
                with open(json_file, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                
                if isinstance(data, list):
                    ruleset = self._convert_sillytavern_format(data, preset_name, RegexScope.PRESET)
                elif isinstance(data, dict) and 'rules' in data:
                    ruleset = RegexRuleset(**data)
                else:
                    continue
                
                # 确保所有规则的 scope 正确
                for rule in ruleset.rules:
                    rule.scope = RegexScope.PRESET
                    rule.presetName = preset_name
                
                self.preset_rulesets[preset_name] = ruleset
                logger.info(f"加载预设规则: {preset_name} ({len(ruleset.rules)} 条规则)")
            except Exception as e:
                logger.error(f"加载预设规则失败 {json_file}: {e}")
    
    def _convert_sillytavern_format(
        self, 
        st_rules: List[dict], 
        name: str,
        scope: RegexScope = RegexScope.GLOBAL
    ) -> RegexRuleset:
        """将 SillyTavern 格式转换为内部格式"""
        rules = []
        for idx, st_rule in enumerate(st_rules):
            find_regex = st_rule.get('findRegex', '')
            pattern, flags = self._parse_st_regex(find_regex)
            
            # 解析 placement（默认为 AI_OUTPUT）
            placement_data = st_rule.get('placement', [2])
            placement = [RegexPlacement(p) for p in placement_data]
            
            rule = RegexRule(
                id=str(uuid4()),
                scriptName=st_rule.get('scriptName', f"{name} 规则 {idx + 1}"),
                findRegex=pattern,
                replaceString=st_rule.get('replaceString', ''),
                trimStrings=st_rule.get('trimStrings', []),
                placement=placement,
                substituteRegex=SubstituteMode(st_rule.get('substituteRegex', 0)),
                markdownOnly=st_rule.get('markdownOnly', False),
                promptOnly=st_rule.get('promptOnly', False),
                runOnEdit=st_rule.get('runOnEdit', True),
                minDepth=st_rule.get('minDepth', 0),
                maxDepth=st_rule.get('maxDepth'),
                scope=scope,
                characterName=name if scope == RegexScope.CHARACTER else None,
                presetName=name if scope == RegexScope.PRESET else None,
                disabled=st_rule.get('disabled', False),
                order=idx
            )
            rules.append(rule)
        
        ruleset = RegexRuleset(
            id=str(uuid4()),
            name=f"{name} 规则集",
            description=f"从 SillyTavern 导入的规则",
            rules=rules,
            isSillyTavernFormat=True
        )
        
        return ruleset
    
    def _parse_st_regex(self, st_regex: str) -> tuple[str, str]:
        """解析 SillyTavern 的正则表达式格式 /pattern/flags"""
        if st_regex.startswith('/') and st_regex.count('/') >= 2:
            parts = st_regex.split('/')
            pattern = '/'.join(parts[1:-1])
            flags = parts[-1] if len(parts) > 2 else ''
            return pattern, flags
        else:
            return st_regex, ''
    
    def apply_rules_by_placement(
        self,
        text: str,
        placement: int,
        character_name: Optional[str] = None,
        preset_name: Optional[str] = None,
        message_depth: int = 0,
        is_for_llm: bool = False,  # ✅ 新增：是否发送给LLM
        is_markdown_rendered: bool = False  # ✅ 新增：是否已Markdown渲染
    ) -> str:
        """
        根据 placement 应用正则规则
        
        Args:
            text: 要处理的文本
            placement: 应用位置（0-5）
            character_name: 当前角色卡名称
            preset_name: 当前预设名称
            message_depth: 消息深度
            is_for_llm: 是否用于发送给 LLM（影响 promptOnly 逻辑）
            is_markdown_rendered: 是否是 Markdown 渲染后的内容（影响 markdownOnly 逻辑）
        
        Returns:
            处理后的文本
        """
        rules = self.get_rules_for_context(character_name, preset_name)
        
        result = text
        for rule in rules:
            # ✅ SillyTavern 逻辑：根据 markdownOnly 和 promptOnly 决定是否应用
            # - 双 false：应用到所有场景（包括保存数据、发送LLM、显示）
            # - markdownOnly=true：只应用于 Markdown 渲染（前端显示）
            # - promptOnly=true：只应用于发送给 LLM
            # - 双 true：应用到所有场景（但不修改存储，由调用方决定）
            
            # 如果是保存数据的场景（is_for_llm=False 且 is_markdown_rendered=False）
            # 只应用双 false 的规则
            if not is_for_llm and not is_markdown_rendered:
                # 保存数据：只应用双 false 的规则
                if rule.markdownOnly or rule.promptOnly:
                    continue
            
            # 如果是发送给 LLM 的场景
            elif is_for_llm and not is_markdown_rendered:
                # 不应用 markdownOnly=true 且 promptOnly=false 的规则
                if rule.markdownOnly and not rule.promptOnly:
                    continue
            
            # 如果是 Markdown 渲染的场景（前端显示）
            elif is_markdown_rendered and not is_for_llm:
                # 不应用 promptOnly=true 且 markdownOnly=false 的规则
                if rule.promptOnly and not rule.markdownOnly:
                    continue
            
            # 检查此规则是否适用于当前 placement
            if placement not in [p.value for p in rule.placement]:
                continue
            
            # 检查消息深度限制
            if message_depth < rule.minDepth:
                continue
            if rule.maxDepth is not None and message_depth > rule.maxDepth:
                continue
            
            # 应用规则
            result = self._apply_single_rule(result, rule)
        
        return result
    
    def _apply_single_rule(self, text: str, rule: RegexRule) -> str:
        """应用单条正则规则"""
        try:
            flags = 0
            if 'i' in rule.findRegex:
                flags |= re.IGNORECASE
            if 'm' in rule.findRegex:
                flags |= re.MULTILINE
            if 's' in rule.findRegex:
                flags |= re.DOTALL
            
            pattern = rule.findRegex.replace('i', '').replace('m', '').replace('s', '')
            
            if rule.substituteRegex == SubstituteMode.REPLACE_FIRST:
                result = re.sub(pattern, rule.replaceString, text, count=1, flags=flags)
            else:
                result = re.sub(pattern, rule.replaceString, text, flags=flags)
            
            for trim_str in rule.trimStrings:
                result = result.replace(trim_str, '')
            
            return result
        except re.error as e:
            logger.error(f"正则表达式错误 [{rule.scriptName}]: {e}")
            return text
    
    def get_rules_for_context(
        self,
        character_name: Optional[str] = None,
        preset_name: Optional[str] = None
    ) -> List[RegexRule]:
        """
        根据上下文获取适用的规则列表
        
        优先级：全局规则 + 角色规则 + 预设规则
        """
        applicable_rules = []
        
        # 1. 加载全局规则
        for ruleset in self.global_rulesets.values():
            for rule in ruleset.rules:
                if not rule.disabled:
                    applicable_rules.append(rule)
        
        # 2. 加载角色卡规则
        if character_name and character_name in self.character_rulesets:
            ruleset = self.character_rulesets[character_name]
            for rule in ruleset.rules:
                if not rule.disabled:
                    applicable_rules.append(rule)
        
        # 3. 加载预设规则
        if preset_name and preset_name in self.preset_rulesets:
            ruleset = self.preset_rulesets[preset_name]
            for rule in ruleset.rules:
                if not rule.disabled:
                    applicable_rules.append(rule)
        
        # 按 order 排序
        applicable_rules.sort(key=lambda r: r.order)
        
        return applicable_rules
    
    def save_ruleset(self, ruleset: RegexRuleset, scope: RegexScope, name: Optional[str] = None):
        """保存规则集到文件"""
        if scope == RegexScope.GLOBAL:
            file_path = self.global_path / f"{ruleset.id}.json"
        elif scope == RegexScope.CHARACTER:
            file_path = self.characters_path / f"{name or 'unknown'}.json"
        elif scope == RegexScope.PRESET:
            file_path = self.presets_path / f"{name or 'unknown'}.json"
        else:
            raise ValueError(f"未知的作用域: {scope}")
        
        with open(file_path, 'w', encoding='utf-8') as f:
            json.dump(ruleset.dict(), f, ensure_ascii=False, indent=2)
        
        logger.info(f"保存规则集到: {file_path}")
    
    def delete_ruleset(self, scope: RegexScope, name: str):
        """删除规则集"""
        if scope == RegexScope.CHARACTER:
            file_path = self.characters_path / f"{name}.json"
        elif scope == RegexScope.PRESET:
            file_path = self.presets_path / f"{name}.json"
        else:
            raise ValueError(f"不能删除全局规则集")
        
        if file_path.exists():
            file_path.unlink()
            logger.info(f"删除规则集: {file_path}")


# 全局实例
regex_service = RegexService()
