import os
import re
import json
import logging
from datetime import datetime
from typing import Optional

logger = logging.getLogger(__name__)


class TemplateScannerService:
    """模板占位符扫描服务 V3
    
    基于56个docx模板实际扫描结果，支持：
    - 从docx文件提取{{}}占位符
    - 与29个标准字段进行匹配
    - 计算覆盖率并生成映射关系
    """

    # 29个标准占位符定义（与standard_placeholders表数据一致）
    STANDARD_PLACEHOLDERS = {
        # A组：案号信息（2个）
        '{{案号-地区简称}}': {'source': 'cases', 'field': 'case_number_region', 'type': 'autoFill', 'category': 'case_number'},
        '{{案号-案件号}}': {'source': 'cases', 'field': 'case_number_seq', 'type': 'autoFill', 'category': 'case_number'},
        # B组：执法单位信息（4个）
        '{{执法单位名称}}': {'source': 'cases', 'field': 'org_name', 'type': 'autoFill', 'category': 'org'},
        '{{单位联系电话}}': {'source': 'cases', 'field': 'org_phone', 'type': 'autoFill', 'category': 'org'},
        '{{单位联系地址}}': {'source': 'cases', 'field': 'org_address', 'type': 'autoFill', 'category': 'org'},
        '{{执法单位地址}}': {'source': 'cases', 'field': 'org_address', 'type': 'autoFill', 'category': 'org'},
        # C组：当事人信息（11个）
        '{{当事人名称}}': {'source': 'cases', 'field': 'party_name', 'type': 'autoFill', 'category': 'party'},
        '{{当事人姓名}}': {'source': 'cases', 'field': 'party_name', 'type': 'autoFill', 'category': 'party'},
        '{{字号名称}}': {'source': 'cases', 'field': 'party_name', 'type': 'autoFill', 'category': 'party'},
        '{{当事人住所}}': {'source': 'cases', 'field': 'address', 'type': 'autoFill', 'category': 'party'},
        '{{当事人住所地址}}': {'source': 'cases', 'field': 'address', 'type': 'autoFill', 'category': 'party'},
        '{{当事人联系电话}}': {'source': 'cases', 'field': 'phone', 'type': 'autoFill', 'category': 'party'},
        '{{统一社会信用代码}}': {'source': 'cases', 'field': 'credit_code', 'type': 'autoFill', 'category': 'party'},
        '{{个体统一社会信用代码}}': {'source': 'cases', 'field': 'credit_code', 'type': 'autoFill', 'category': 'party'},
        '{{法定代表人}}': {'source': 'cases', 'field': 'legal_representative', 'type': 'autoFill', 'category': 'party'},
        '{{身份证件号码}}': {'source': 'cases', 'field': 'party_id_number', 'type': 'autoFill', 'category': 'party'},
        '{{法人身份证号码}}': {'source': 'cases', 'field': 'party_id_number', 'type': 'autoFill', 'category': 'party'},
        # D组：执法人员信息（4个）
        '{{主办}}': {'source': 'cases', 'field': 'main_officer->name', 'type': 'autoFill', 'category': 'officer'},
        '{{协办}}': {'source': 'cases', 'field': 'assistant_officer->name', 'type': 'autoFill', 'category': 'officer'},
        '{{主办证号}}': {'source': 'cases', 'field': 'main_officer->badge_number', 'type': 'autoFill', 'category': 'officer'},
        '{{协办证号}}': {'source': 'cases', 'field': 'assistant_officer->badge_number', 'type': 'autoFill', 'category': 'officer'},
        # E组：案由与法律依据（5个）
        '{{案由}}': {'source': 'case_causes_detail', 'field': 'cause_name_merged', 'type': 'causeFill', 'category': 'cause'},
        '{{案由-法律依据-违法条款}}': {'source': 'case_cause_laws', 'field': 'violation_laws_full', 'type': 'causeFill', 'category': 'cause'},
        '{{案由-法律依据-处罚条款}}': {'source': 'case_cause_laws', 'field': 'penalty_laws_clause', 'type': 'causeFill', 'category': 'cause'},
        '{{案由-法律规定-处罚条款}}': {'source': 'case_cause_laws', 'field': 'penalty_laws_clause', 'type': 'causeFill', 'category': 'cause'},
        '{{案由-法律依据-处罚-详细内容}}': {'source': 'case_cause_laws', 'field': 'penalty_content_detail', 'type': 'causeFill', 'category': 'cause'},
        # F组：其他（3个）
        '{{案件名称}}': {'source': 'cases', 'field': 'title', 'type': 'autoFill', 'category': 'other'},
        '{{接收材料日期}}': {'source': 'cases', 'field': 'receive_date', 'type': 'autoFill', 'category': 'other'},
        '{{案件描述}}': {'source': 'cases', 'field': 'description', 'type': 'autoFill', 'category': 'other'},
    }

    def __init__(self, template_base_dir: str = None):
        if template_base_dir:
            self.template_base_dir = template_base_dir
        else:
            from ..core.config import settings
            from ..core import paths
            base = settings.TEMPLATE_BASE_DIR
            self.template_base_dir = base if os.path.isabs(base) else paths.resolve_resource_path(base)

    def extract_placeholders_from_docx(self, file_path: str) -> dict:
        """从docx文件提取所有{{}}占位符
        
        Args:
            file_path: docx文件路径
            
        Returns:
            {
                'total': int,           # 占位符总出现次数
                'unique': list[str],    # 唯一占位符列表
                'unique_count': int,    # 唯一占位符数
                'occurrences': dict     # 每个占位符的出现次数 {name: count}
            }
        """
        result = {
            'total': 0,
            'unique': [],
            'unique_count': 0,
            'occurrences': {}
        }
        
        if not os.path.exists(file_path):
            logger.warning(f"文件不存在: {file_path}")
            return result
        
        try:
            from docx import Document
            from docxtpl import DocxTemplate
            
            all_matches = []
            
            # 方法1: 使用docxtpl解析XML（更准确，能捕获嵌套在表格中的占位符）
            try:
                doc = DocxTemplate(file_path)
                from docx.opc.constants import RELATIONSHIP_TYPE as RT
                parts_rels = {}
                for rel in doc.part.rels.values():
                    parts_rels[rel.rId] = rel.target_part
                for rel_id, part in parts_rels.items():
                    if hasattr(part, '_element'):
                        xml = part._element.xml
                        for match in re.finditer(r'\{\{(.+?)\}\}', xml):
                            field = match.group(1).strip()
                            if field and not field.startswith("#") and not field.startswith("@"):
                                all_matches.append(field)
            except Exception as e:
                logger.debug(f"docxtpl解析失败，尝试备用方法: {e}")
            
            # 方法2: 使用python-docx遍历段落和表格（备用）
            if not all_matches:
                try:
                    doc = Document(file_path)
                    for p in doc.paragraphs:
                        for match in re.finditer(r'\{\{(.+?)\}\}', p.text):
                            field = match.group(1).strip()
                            if field and not field.startswith("#") and not field.startswith("@"):
                                all_matches.append(field)
                    for table in doc.tables:
                        for row in table.rows:
                            for cell in row.cells:
                                for match in re.finditer(r'\{\{(.+?)\}\}', cell.text):
                                    field = match.group(1).strip()
                                    if field and not field.startswith("#") and not field.startswith("@"):
                                        all_matches.append(field)
                except Exception as e:
                    logger.error(f"python-docx解析也失败: {e}")
            
            # 统计结果
            for field in all_matches:
                result['total'] += 1
                if field not in result['occurrences']:
                    result['occurrences'][field] = 0
                    result['unique'].append(field)
                result['occurrences'][field] += 1
            
            result['unique_count'] = len(result['unique'])
            
        except ImportError:
            logger.error("python-docx 或 docxtpl 未安装")
        except Exception as e:
            logger.error(f"提取占位符时出错: {e}")
        
        return result

    def calculate_coverage(self, found_placeholders: list[str]) -> dict:
        """计算覆盖率
        
        Args:
            found_placeholders: 扫描到的唯一占位符列表
            
        Returns:
            {
                'total_standard': int,      # 标准占位符总数 (29)
                'mapped_count': int,        # 已映射到标准字段的数量
                'unmapped': list[str],      # 未映射的占位符列表
                'coverage_percent': float,  # 覆盖率百分比
                'status': str               # ready/partial/empty/not_scanned
            }
        """
        total_standard = len(self.STANDARD_PLACEHOLDERS)  # 29
        
        if not found_placeholders:
            return {
                'total_standard': total_standard,
                'mapped_count': 0,
                'unmapped': [],
                'coverage_percent': 0.0,
                'status': 'empty'
            }
        
        mapped = []
        unmapped = []
        
        for ph in found_placeholders:
            key_with_braces = f'{{{{{ph}}}}}' if not ph.startswith('{{') else ph
            if key_with_braces in self.STANDARD_PLACEHOLDERS:
                mapped.append(key_with_braces)
            else:
                unmapped.append(ph)
        
        mapped_count = len(mapped)
        coverage_percent = round((mapped_count / len(found_placeholders)) * 100, 1) if found_placeholders else 0
        
        if unmapped:
            status = 'partial'
        elif found_placeholders:
            status = 'ready'
        else:
            status = 'empty'
        
        return {
            'total_standard': total_standard,
            'mapped_count': mapped_count,
            'unmapped': unmapped,
            'coverage_percent': coverage_percent,
            'status': status
        }

    def build_mapping(self, found_placeholders: list[str], occurrences: dict = None) -> dict:
        """自动构建映射关系
        
        Args:
            found_placeholders: 扫描到的唯一占位符列表（不含{{}}）
            occurrences: 每个占位符的出现次数 {name: count}
            
        Returns:
            JSON格式的映射关系字典
        """
        mapping = {}
        occurrences = occurrences or {}
        
        for ph in found_placeholders:
            key_with_braces = f'{{{{{ph}}}}}' if not ph.startswith('{{') else ph
            if key_with_braces in self.STANDARD_PLACEHOLDERS:
                std_info = self.STANDARD_PLACEHOLDERS[key_with_braces]
                mapping[key_with_braces] = {
                    'source': std_info['source'],
                    'field': std_info['field'],
                    'type': std_info['type'],
                    'category': std_info['category'],
                    'mapped': True,
                    'occurrences': occurrences.get(ph, 1)
                }
            else:
                mapping[ph] = {
                    'source': None,
                    'field': None,
                    'type': None,
                    'category': 'unknown',
                    'mapped': False,
                    'occurrences': occurrences.get(ph, 1),
                    'suggestion': '非标准占位符，需手动确认或忽略'
                }
        
        return mapping

    def scan_template_file(self, file_path: str) -> dict:
        """扫描单个模板文件（完整流程）
        
        Args:
            file_path: docx文件完整路径
            
        Returns:
            完整的扫描结果字典
        """
        filename = os.path.basename(file_path)
        
        # Step 1: 提取占位符
        extract_result = self.extract_placeholders_from_docx(file_path)
        
        # Step 2: 计算覆盖率
        coverage_result = self.calculate_coverage(extract_result['unique'])
        
        # Step 3: 构建映射
        mapping = self.build_mapping(extract_result['unique'], extract_result['occurrences'])
        
        # 构建占位符详情列表
        placeholder_details = []
        for ph in extract_result['unique']:
            key_with_braces = f'{{{{{ph}}}}}' if not ph.startswith('{{') else ph
            info = mapping.get(key_with_braces, mapping.get(ph, {}))
            placeholder_details.append({
                'name': key_with_braces,
                'occurrences': extract_result['occurrences'].get(ph, 1),
                'mapped': info.get('mapped', False),
                'source_table': info.get('source'),
                'source_field': info.get('field'),
                'fill_type': info.get('type')
            })
        
        return {
            'filename': filename,
            'total_placeholders': extract_result['total'],
            'unique_count': extract_result['unique_count'],
            'mapped_count': coverage_result['mapped_count'],
            'coverage_percent': coverage_result['coverage_percent'],
            'coverage_status': coverage_result['status'],
            'placeholders': placeholder_details,
            'unmapped': coverage_result['unmapped'],
            'mapping': mapping,
            'scan_time': datetime.now().isoformat()
        }

    def scan_all_templates(self) -> dict:
        """批量扫描目录下所有docx模板
        
        Returns:
            {
                'total_files': int,
                'scanned_count': int,
                'error_count': int,
                'results': list[dict],  # 每个文件的扫描结果
                'summary': {...}       # 汇总统计
            }
        """
        results = []
        errors = []
        total_files = 0
        
        if not os.path.isdir(self.template_base_dir):
            logger.error(f"模板目录不存在: {self.template_base_dir}")
            return {'total_files': 0, 'scanned_count': 0, 'error_count': 0, 'results': [], 'summary': {}}
        
        for filename in sorted(os.listdir(self.template_base_dir)):
            if not filename.lower().endswith('.docx'):
                continue
            
            total_files += 1
            file_path = os.path.join(self.template_base_dir, filename)
            
            try:
                result = self.scan_template_file(file_path)
                results.append(result)
            except Exception as e:
                logger.error(f"扫描文件失败 [{filename}]: {e}")
                errors.append({'filename': filename, 'error': str(e)})
        
        # 汇总统计
        status_counts = {'ready': 0, 'partial': 0, 'empty': 0, 'not_scanned': 0}
        category_stats = {}
        
        for r in results:
            status = r.get('coverage_status', 'not_scanned')
            status_counts[status] = status_counts.get(status, 0) + 1
        
        summary = {
            'total_files': total_files,
            'scanned_count': len(results),
            'error_count': len(errors),
            'by_status': status_counts,
            'average_coverage': round(
                sum(r.get('coverage_percent', 0) for r in results) / len(results), 1
            ) if results else 0
        }
        
        return {
            'total_files': total_files,
            'scanned_count': len(results),
            'error_count': len(errors),
            'results': results,
            'errors': errors,
            'summary': summary
        }


class DocumentGeneratorV3:
    """零AI版文书生成器 V3
    
    基于29个实际占位符的纯数据驱动生成
    支持测试生成（使用模拟数据）和生产生成（使用真实案件数据）
    """

    # 29个标准占位符默认值（用于测试生成）
    MOCK_DATA = {
        # A组：案号信息
        '{{案件名称}}': '涉嫌发布虚假广告案',
        '{{案号-地区简称}}': 'X市监',
        '{{案号-案件号}}': '〔2026〕第001号',
        # B组：执法单位
        '{{执法单位名称}}': 'XX市市场监督管理局',
        '{{单位联系电话}}': '010-12345678',
        '{{单位联系地址}}': 'XX市XX区XX路123号',
        '{{执法单位地址}}': 'XX市XX区XX路123号',
        # C组：当事人
        '{{当事人名称}}': 'XX商贸有限公司',
        '{{当事人姓名}}': '张三',
        '{{字号名称}}': '张三便利店',
        '{{当事人住所}}': 'XX市XX区XX街道XX号',
        '{{当事人住所地址}}': 'XX市XX区XX街道XX号',
        '{{当事人联系电话}}': '138****8888',
        '{{统一社会信用代码}}': '91110000MA00XXXXX1',
        '{{个体统一社会信用代码}}': '92110000MA00XXXXX2',
        '{{法定代表人}}': '李四',
        '{{身份证件号码}}': '11010119900101XXXX',
        '{{法人身份证号码}}': '11010119850101XXXX',
        # D组：执法人员
        '{{主办}}': '王五',
        '{{协办}}': '赵六',
        '{{主办证号}}': 'A00000001',
        '{{协办证号}}': 'A00000002',
        # E组：案由与法条
        '{{案由}}': '涉嫌发布虚假广告',
        '{{案由-法律依据-违法条款}}': 
            '《中华人民共和国广告法》第四条第一款、第二十八条第二款',
        '{{案由-法律依据-处罚条款}}': 
            '《中华人民共和国广告法》第五十五条',
        '{{案由-法律规定-处罚条款}}': 
            '《中华人民共和国广告法》第五十五条',
        '{{案由-法律依据-处罚-详细内容}}': 
            '责令停止发布广告，在相应范围内消除影响，处广告费用三倍罚款',
        # F组：其他
        '{{接收材料日期}}': '2026年05月20日',
        '{{案件描述}}': '当事人在其经营的网店中发布的商品广告含有虚假内容',
    }

    def __init__(self, template_base_dir: str = None, output_dir: str = None):
        self.template_base_dir = template_base_dir
        self.output_dir = output_dir or os.path.join(
            os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))),
            "files", "documents", "test"
        )

    async def generate_test_document(self, template_id: str, template_file_path: str = None) -> dict:
        """测试生成：使用模拟数据填充模板
        
        Args:
            template_id: 模板ID
            template_file_path: 模板文件路径（可选）
            
        Returns:
            {
                'success': bool,
                'file_path': str,
                'filled_count': int,
                'message': str
            }
        """
        import uuid
        from datetime import date
        from docxtpl import DocxTemplate
        
        context = self._build_mock_context()
        
        try:
            doc = DocxTemplate(template_file_path)
            doc.render(context)
            
            today_dir = date.today().strftime("%Y-%m")
            file_dir = os.path.join(self.output_dir, today_dir)
            os.makedirs(file_dir, exist_ok=True)
            
            file_id = f"test_{template_id}_{uuid.uuid4().hex[:8]}"
            output_path = os.path.join(file_dir, f"{file_id}.docx")
            doc.save(output_path)
            
            return {
                'success': True,
                'file_path': output_path,
                'file_url': f"/api/admin/files/{today_dir}/{file_id}.docx",
                'filled_count': len(context),
                'message': f"测试生成成功，已填充{len(context)}个占位符"
            }
        except Exception as e:
            logger.error(f"测试生成失败: {e}")
            return {
                'success': False,
                'file_path': None,
                'filled_count': 0,
                'message': f"生成失败: {str(e)}"
            }

    def _build_mock_context(self, custom_data: dict = None) -> dict:
        """构建模拟渲染上下文（29个字段）"""
        context = self.MOCK_DATA.copy()
        if custom_data:
            context.update(custom_data)
        return context

    async def generate_from_case_data(self, case_data: dict, cause_data: list = None, 
                                       template_file_path: str = None) -> dict:
        """生产生成：使用真实案件数据填充模板
        
        Args:
            case_data: cases表数据字典
            cause_data: 案由详情列表（来自case_causes_detail + laws）
            template_file_path: 模板文件路径
            
        Returns:
            生成结果字典
        """
        import uuid
        from datetime import date
        from docxtpl import DocxTemplate
        
        context = self._build_render_context(case_data, cause_data or [])
        
        try:
            doc = DocxTemplate(template_file_path)
            doc.render(context)
            
            today_dir = date.today().strftime("%Y-%m")
            file_dir = os.path.join(self.output_dir, today_dir)
            os.makedirs(file_dir, exist_ok=True)
            
            file_id = f"{uuid.uuid4().hex[:8]}"
            output_path = os.path.join(file_dir, f"{file_id}.docx")
            doc.save(output_path)
            
            return {
                'success': True,
                'file_path': output_path,
                'filled_count': len(context),
                'message': f"文书生成成功，已填充{len(context)}个占位符"
            }
        except Exception as e:
            logger.error(f"文书生成失败: {e}")
            return {
                'success': False,
                'file_path': None,
                'filled_count': 0,
                'message': f"生成失败: {str(e)}"
            }

    def _build_render_context(self, case_data: dict, cause_data: list) -> dict:
        """从真实案件数据构建渲染上下文"""
        def _json_get(json_str, key, default=''):
            try:
                d = json.loads(json_str) if json_str else {}
                return d.get(key, default)
            except:
                return default
        
        causes_merged = '、'.join([c.get('cause_name', '') for c in cause_data if c.get('cause_name')])
        
        violation_parts = []
        penalty_clause_parts = []
        penalty_detail_parts = []
        
        for c in cause_data:
            if c.get('violation_refs'):
                violation_parts.append(c['violation_refs'])
            if c.get('penalty_clause'):
                penalty_clause_parts.append(c['penalty_clause'])
            if c.get('penalty_detail'):
                penalty_detail_parts.append(c['penalty_detail'])
        
        receive_date = case_data.get('receive_date') or ''
        if isinstance(receive_date, str) and 'T' in receive_date:
            receive_date = receive_date.split('T')[0]
        
        return {
            # autoFill (24个 from cases table)
            '{{案件名称}}': case_data.get('title') or '',
            '{{案号-地区简称}}': case_data.get('case_number_region') or '',
            '{{案号-案件号}}': case_data.get('case_number_seq') or '',
            '{{案由}}': causes_merged,
            '{{接收材料日期}}': str(receive_date)[:10],
            '{{案件描述}}': case_data.get('description') or '',
            '{{执法单位名称}}': case_data.get('org_name') or '',
            '{{单位联系电话}}': case_data.get('org_phone') or '',
            '{{单位联系地址}}': case_data.get('org_address') or '',
            '{{执法单位地址}}': case_data.get('org_address') or '',
            '{{当事人名称}}': case_data.get('party_name') or '',
            '{{当事人姓名}}': case_data.get('party_name') or '',
            '{{字号名称}}': case_data.get('party_name') or '',
            '{{当事人住所}}': case_data.get('address') or '',
            '{{当事人住所地址}}': case_data.get('address') or '',
            '{{当事人联系电话}}': case_data.get('phone') or '',
            '{{统一社会信用代码}}': case_data.get('credit_code') or '',
            '{{个体统一社会信用代码}}': case_data.get('credit_code') or '',
            '{{法定代表人}}': case_data.get('legal_representative') or '',
            '{{身份证件号码}}': case_data.get('party_id_number') or '',
            '{{法人身份证号码}}': case_data.get('party_id_number') or '',
            '{{主办}}': _json_get(case_data.get('main_officer'), 'name'),
            '{{协办}}': _json_get(case_data.get('assistant_officer'), 'name'),
            '{{主办证号}}': _json_get(case_data.get('main_officer'), 'badge_number'),
            '{{协办证号}}': _json_get(case_data.get('assistant_officer'), 'badge_number'),
            # causeFill (5个 from case_causes_detail + laws)
            '{{案由-法律依据-违法条款}}': '\n'.join(violation_parts),
            '{{案由-法律依据-处罚条款}}': '；'.join(penalty_clause_parts),
            '{{案由-法律规定-处罚条款}}': '；'.join(penalty_clause_parts),
            '{{案由-法律依据-处罚-详细内容}}': '\n\n'.join(penalty_detail_parts),
        }
