#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
H5 到小程序样式对比检查工具

用法：
    python check_h5_to_mp.py --h5 h5.html --wxss mp.wxss --output report.md
"""

import re
import argparse
from pathlib import Path
from typing import Dict, List, Set
from dataclasses import dataclass
from enum import Enum
from datetime import datetime


class IssueLevel(Enum):
    """问题级别"""
    CRITICAL = "❌ 严重"  # 差异 > 20%
    WARNING = "⚠️ 警告"   # 差异 10-20%
    INFO = "ℹ️ 提示"      # 差异 < 10%
    OK = "✅ 正确"        # 差异 < 2%


@dataclass
class StyleIssue:
    """样式问题"""
    level: IssueLevel
    category: str
    tailwind_class: str
    property_name: str
    h5_value: str
    expected_rpx: str
    actual_rpx: str
    difference: float
    location: str


# Tailwind CSS v3 配置
TAILWIND_CONFIG = {
    'text': {
        'text-xs': {'font-size': '12px', 'line-height': '16px'},
        'text-sm': {'font-size': '14px', 'line-height': '20px'},
        'text-base': {'font-size': '16px', 'line-height': '24px'},
        'text-lg': {'font-size': '18px', 'line-height': '28px'},
        'text-xl': {'font-size': '20px', 'line-height': '28px'},
        'text-2xl': {'font-size': '24px', 'line-height': '32px'},
    },
    'spacing': {
        'p-2': '8px', 'p-3': '12px', 'p-4': '16px',
        'm-2': '8px', 'm-3': '12px', 'm-4': '16px',
        'gap-2': '8px', 'gap-3': '12px',
        'ml-11': '44px', 'mb-2': '8px', 'mb-3': '12px',
        'pt-2': '8px', 'pt-3': '12px',
    },
}

PX_TO_RPX = 1.8204  # 412px 设备


def px_to_rpx(px_value: str) -> float:
    """将 px 值转换为 rpx"""
    if not px_value or px_value == '0':
        return 0
    match = re.search(r'([\d.]+)', px_value)
    if match:
        return round(float(match.group(1)) * PX_TO_RPX, 2)
    return 0


def rpx_to_px(rpx_value: str) -> float:
    """将 rpx 值转换为 px"""
    if not rpx_value or rpx_value == '0':
        return 0
    match = re.search(r'([\d.]+)', rpx_value)
    if match:
        return round(float(match.group(1)) / PX_TO_RPX, 2)
    return 0


def extract_tailwind_classes(html_content: str) -> Dict[str, Set[str]]:
    """提取 HTML 中所有 Tailwind 类"""
    classes = {'text': set(), 'spacing': set(), 'custom': set()}
    
    class_attrs = re.findall(r'class="([^"]+)"', html_content)
    for class_str in class_attrs:
        for cls in class_str.split():
            if cls.startswith('text-'):
                if '[' in cls:
                    classes['custom'].add(cls)
                else:
                    classes['text'].add(cls)
            elif re.match(r'^[pm][tblrxy]?-', cls) or cls.startswith('gap-'):
                classes['spacing'].add(cls)
    
    return classes


def extract_wxss_styles(wxss_content: str) -> Dict[str, Dict[str, str]]:
    """提取 WXSS 中所有样式"""
    styles = {}
    pattern = r'\.([a-zA-Z0-9_-]+(?:--[a-zA-Z0-9_-]+)?)\s*\{([^}]+)\}'
    matches = re.findall(pattern, wxss_content, re.MULTILINE)
    
    for class_name, rules in matches:
        styles[class_name] = {}
        for rule in rules.split(';'):
            rule = rule.strip()
            if ':' in rule:
                prop, value = rule.split(':', 1)
                value = re.sub(r'/\*.*?\*/', '', value).strip()
                styles[class_name][prop.strip()] = value
    
    return styles


def check_global_styles(wxss_content: str) -> List[StyleIssue]:
    """检查全局样式"""
    issues = []
    
    page_match = re.search(r'page\s*\{([^}]+)\}', wxss_content)
    if page_match and 'line-height:' in page_match.group(1):
        lh_match = re.search(r'line-height:\s*([\d.]+)', page_match.group(1))
        if lh_match:
            issues.append(StyleIssue(
                level=IssueLevel.CRITICAL,
                category='global',
                tailwind_class='page',
                property_name='line-height',
                h5_value='无全局设置',
                expected_rpx='无',
                actual_rpx=lh_match.group(1),
                difference=100.0,
                location='page 全局样式'
            ))
    
    return issues


def check_text_classes(tailwind_classes: Set[str], wxss_styles: Dict) -> List[StyleIssue]:
    """检查文本类"""
    issues = []
    
    for tw_class in tailwind_classes:
        if tw_class not in TAILWIND_CONFIG['text']:
            continue
        
        config = TAILWIND_CONFIG['text'][tw_class]
        h5_font_size = config['font-size']
        h5_line_height = config['line-height']
        expected_fs_rpx = px_to_rpx(h5_font_size)
        expected_lh_rpx = px_to_rpx(h5_line_height)
        
        for class_name, props in wxss_styles.items():
            if 'font-size' not in props:
                continue
            
            actual_fs = props['font-size']
            actual_px = rpx_to_px(actual_fs)
            expected_px = float(h5_font_size.replace('px', ''))
            
            if abs(actual_px - expected_px) < 2:
                # 检查 line-height
                if 'line-height' not in props:
                    issues.append(StyleIssue(
                        level=IssueLevel.CRITICAL,
                        category='text',
                        tailwind_class=tw_class,
                        property_name='line-height',
                        h5_value=h5_line_height,
                        expected_rpx=f'{expected_lh_rpx}rpx',
                        actual_rpx='缺失',
                        difference=100.0,
                        location=f'.{class_name}'
                    ))
                else:
                    actual_lh = props['line-height']
                    actual_lh_px = rpx_to_px(actual_lh)
                    expected_lh_px = float(h5_line_height.replace('px', ''))
                    diff = abs(actual_lh_px - expected_lh_px) / expected_lh_px * 100
                    
                    if diff > 2:
                        level = IssueLevel.CRITICAL if diff > 20 else (
                            IssueLevel.WARNING if diff > 10 else IssueLevel.INFO)
                        issues.append(StyleIssue(
                            level=level,
                            category='text',
                            tailwind_class=tw_class,
                            property_name='line-height',
                            h5_value=h5_line_height,
                            expected_rpx=f'{expected_lh_rpx}rpx',
                            actual_rpx=actual_lh,
                            difference=diff,
                            location=f'.{class_name}'
                        ))
    
    return issues


def generate_report(issues: List[StyleIssue], output_file: Path):
    """生成 Markdown 报告"""
    critical = [i for i in issues if i.level == IssueLevel.CRITICAL]
    warning = [i for i in issues if i.level == IssueLevel.WARNING]
    info = [i for i in issues if i.level == IssueLevel.INFO]
    
    report = f"""# H5 到小程序样式对比报告

> 生成时间：{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}

## 📊 问题统计

- ❌ 严重问题：{len(critical)} 个
- ⚠️ 警告：{len(warning)} 个
- ℹ️ 提示：{len(info)} 个
- **总计**：{len(issues)} 个

---

## ❌ 严重问题（{len(critical)} 个）

"""
    
    for issue in critical:
        report += f"""### {issue.level.value} {issue.tailwind_class} - {issue.property_name}

- **位置**：{issue.location}
- **H5 值**：{issue.h5_value}
- **应该是**：{issue.expected_rpx}
- **实际是**：{issue.actual_rpx}
- **差异**：{issue.difference:.1f}%

"""
    
    if warning:
        report += f"\n## ⚠️ 警告（{len(warning)} 个）\n\n"
        for issue in warning:
            report += f"- {issue.tailwind_class} - {issue.property_name}: 差异 {issue.difference:.1f}% ({issue.location})\n"
    
    if info:
        report += f"\n## ℹ️ 提示（{len(info)} 个）\n\n"
        for issue in info:
            report += f"- {issue.tailwind_class} - {issue.property_name}: 差异 {issue.difference:.1f}% ({issue.location})\n"
    
    output_file.write_text(report, encoding='utf-8')


def main():
    import sys
    import io
    
    # 修复 Windows 控制台编码问题
    if sys.platform == 'win32':
        sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
        sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
    
    parser = argparse.ArgumentParser(description='H5 到小程序样式对比检查工具')
    parser.add_argument('--h5', required=True, help='H5 HTML 文件路径')
    parser.add_argument('--wxss', required=True, help='小程序 WXSS 文件路径')
    parser.add_argument('--output', default='report.md', help='输出报告路径')
    args = parser.parse_args()
    
    h5_file = Path(args.h5)
    wxss_file = Path(args.wxss)
    output_file = Path(args.output)
    
    if not h5_file.exists():
        print(f"[ERROR] H5 文件不存在：{h5_file}")
        return
    if not wxss_file.exists():
        print(f"[ERROR] WXSS 文件不存在：{wxss_file}")
        return
    
    print("[1/6] 读取文件...")
    h5_content = h5_file.read_text(encoding='utf-8')
    wxss_content = wxss_file.read_text(encoding='utf-8')
    
    print("[2/6] 提取 Tailwind 类...")
    tailwind_classes = extract_tailwind_classes(h5_content)
    print(f"      - text 类：{len(tailwind_classes['text'])} 个")
    
    print("[3/6] 提取 WXSS 样式...")
    wxss_styles = extract_wxss_styles(wxss_content)
    print(f"      - 样式类：{len(wxss_styles)} 个")
    
    print("[4/6] 检查全局样式...")
    issues = check_global_styles(wxss_content)
    
    print("[5/6] 检查文本类...")
    issues.extend(check_text_classes(tailwind_classes['text'], wxss_styles))
    
    print(f"\n[统计] 发现 {len(issues)} 个问题")
    print("[6/6] 生成报告...")
    generate_report(issues, output_file)
    print(f"[完成] 报告已生成：{output_file}")


if __name__ == '__main__':
    main()
