#!/usr/bin/env python
# coding: utf-8
# Copyright (c) 2026 Huawei Technologies Co., Ltd.
# openUBMC is licensed under Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#         http://license.coscl.org.cn/MulanPSL2
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.

import re
import html
from collections import defaultdict
from typing import Optional, Tuple, List, Union

from .base import BaseValidator


class InteropValidator(BaseValidator):
    """Redfish Interop 互操作性验证器"""

    @property
    def name(self) -> str:
        return "interop"

    @property
    def package_name(self) -> str:
        return "redfish_interop_validator"

    @property
    def command_name(self) -> str:
        return "rf_interop_validator"

    @property
    def supported_formats(self) -> list:
        """支持的解析格式：仅 html"""
        return ["html"]

    def _filter_error_warning_blocks(self, block: str, ignore_types: list, result_counter: dict) -> str:
        """
        过滤 URI 块中的 error 或 warning 块
        :param block: URI 块内容
        :param ignore_types: 忽略类型列表，支持 ["error"]、["warning"]、["not_tested"] 或组合
        :param result_counter: 结果计数器
        :return: 过滤后的块

        说明：
        - error/warning: 过滤对应块 + 移除标题栏计数显示
        - not_tested: 仅移除标题栏计数显示，不过滤块
        """
        filtered_block = block

        if "error" in ignore_types:
            # 过滤 error 块：<tr><td class="fail log">...</td></tr>
            pattern = r'<tr>\s*<td class="fail log">.*?</td>\s*</tr>'
            filtered_block = re.sub(pattern, '', filtered_block, flags=re.DOTALL)

        if "warning" in ignore_types:
            # 过滤 warning 块：<tr><td class="warn log">.*?</td>\s*</tr>
            pattern = r'<tr>\s*<td class="warn log">.*?</td>\s*</tr>'
            filtered_block = re.sub(pattern, '', filtered_block, flags=re.DOTALL)

        # 过滤标题栏中的 error/warning/not_tested 计数显示
        # 格式：<td class="log"><div class="fail"><b>error: </b>1</div><div class="warn"><b>warning: </b>1</div></td>
        # not_tested 格式：<div><b>not_tested:</b>5</div>
        if "error" in ignore_types or "warning" in ignore_types or "not_tested" in ignore_types:
            # 匹配并移除 div 元素
            def remove_count_divs(match):
                td_content = match.group(0)
                # 移除 <div class="fail"><b>error: </b>1</div>
                if "error" in ignore_types:
                    td_content = re.sub(r'<div class="fail"><b>error:\s*</b>\s*\d+\s*</div>', '', td_content)
                # 移除 <div class="warn"><b>warning: </b>1</div>
                if "warning" in ignore_types:
                    td_content = re.sub(r'<div class="warn"><b>warning:\s*</b>\s*\d+\s*</div>', '', td_content)
                # 移除 <div><b>not_tested:</b>5</div>
                if "not_tested" in ignore_types:
                    td_content = re.sub(r'<div><b>not_tested:\s*</b>\s*\d+\s*</div>', '', td_content)
                # 如果 td 内容为空，返回空的 td
                if re.match(r'<td class="log">\s*</td>', td_content):
                    return '<td class="log"></td>'
                return td_content

            # 匹配包含 error/warning/not_tested 计数的 td
            pattern = r'<td class="log">(<div class="fail"><b>error:.*?</div>|<div><b>pass:.*?</div>|<div class="warn"><b>warning:.*?</div>|<div><b>not_tested:.*?</div>)+</td>'
            filtered_block = re.sub(pattern, remove_count_divs, filtered_block, flags=re.DOTALL)

        return filtered_block

    def _print_txt_summary(self, lines: list, result_counter: dict):
        """
        美化输出TXT摘要信息到shell
        :param lines: TXT文件行列表
        :param result_counter: 结果统计计数器
        """
        # 提取关键信息
        target_uri = ""
        profile = ""
        redfish_version = ""
        service_description = ""

        for line in lines:
            if "Target URI:" in line:
                target_uri = line.split("Target URI:")[-1].strip()
            elif "profile:" in line and ".json" in line:
                profile = line.split("profile:")[-1].strip()
            elif "Redfish Version of Service:" in line:
                redfish_version = line.split("Redfish Version of Service:")[-1].strip()
            elif "Description of service:" in line:
                service_description = line.split("Description of service:")[-1].strip()

        # 输出美化的摘要
        print("\n" + "=" * 60)
        print(" " * 12 + "Redfish Interop 校验结果报告")
        print("=" * 60)

        if target_uri:
            print(f"\n目标 URI: {target_uri}")
        if service_description:
            print(f"服务描述: {service_description}")
        if redfish_version:
            print(f"Redfish 版本: {redfish_version}")
        if profile:
            print(f"测试配置: {profile}")

        print(f"\n测试统计:")
        print(f"   [PASS] 通过: {result_counter.get('pass', 0)}")
        print(f"   [FAIL] 失败: {result_counter.get('fail', 0)}")
        print(f"   [WARN] 警告: {result_counter.get('warning', 0)}")
        print(f"   [SKIP] 跳过: {result_counter.get('not_tested', 0)}")

        total = sum([result_counter.get(k, 0) for k in ['pass', 'fail', 'warning', 'not_tested']])
        if total > 0:
            pass_rate = (result_counter.get('pass', 0) / total) * 100
            print(f"\n通过率: {pass_rate:.1f}%")

        print("=" * 60 + "\n")

    def parse_tsv_excluded(self, file_path: str, white_list: Optional[dict] = None):
        raise NotImplementedError("Interop validator TSV 解析尚未实现")

    def parse_html_excluded(self, file_path: str, white_list: Optional[List[dict]] = None) -> Tuple[str, dict]:
        """
        解析HTML文件并返回过滤后的内容和统计结果
        :param file_path: HTML文件路径
        :param white_list: 白名单列表，每个元素是包含 URI、Name 或 Ignore 的字典
                          URI 支持正则表达式（包含 .*()[]{}$+?|\\\\ 等特殊字符时自动识别）
        :return: (过滤后的HTML内容, 统计结果字典)

        白名单规则：
        1. 仅配置 URI：过滤掉整个块（支持正则表达式）
        2. 配置 URI 和 Name：精确过滤对应行（URI 支持正则表达式）
        3. 配置 URI 和 Ignore：
           - error/warning: 过滤对应块 + 移除标题栏计数显示
           - not_tested: 仅移除标题栏计数显示，不过滤块
        4. 配置 URI、Name 和 Ignore：先过滤 error/warning 块，再过滤匹配的 Name 行
        """
        if white_list is None:
            white_list = []

        result_counter = defaultdict(int)
        filtered_uri_count = 0
        filtered_name_count = 0
        filtered_error_count = 0
        filtered_warning_count = 0
        filtered_not_tested_count = 0

        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                html_content = f.read()

            # 构建过滤规则
            # uri_only_rules: 只有 URI 的规则，过滤整个 URI 测试结果（精确匹配）
            # uri_only_patterns: URI 正则表达式模式，过滤整个 URI 测试结果
            # uri_combined_rules: URI 对应的规则，包含 names 和 ignore（精确匹配）
            # uri_combined_patterns: URI 正则表达式对应的规则，包含 names 和 ignore
            uri_only_rules = []
            uri_only_patterns = []
            uri_combined_rules = {}
            uri_combined_patterns = []

            for rule in white_list:
                uri = rule.get("URI", "")
                name = rule.get("Name", "")
                ignore = rule.get("Ignore", [])

                if not uri:
                    continue

                # 检查是否是正则表达式（包含特殊字符）
                is_regex = any(c in uri for c in ['.*', '(', ')', '[', ']', '{', '}', '^', '$', '+', '?', '|', '\\'])

                # 如果有 Name 或 Ignore，添加到组合规则中
                if name or ignore:
                    rule_data = {"pattern": uri, "names": [], "ignore": []}
                    # Name 可能是字符串或列表
                    if name:
                        if isinstance(name, list):
                            rule_data["names"].extend(name)
                        else:
                            rule_data["names"].append(name)
                    if ignore:
                        rule_data["ignore"] = [item.lower() for item in ignore]

                    if is_regex:
                        uri_combined_patterns.append(rule_data)
                    else:
                        if uri not in uri_combined_rules:
                            uri_combined_rules[uri] = {"names": [], "ignore": []}
                        # Name 可能是字符串或列表
                        if name:
                            if isinstance(name, list):
                                uri_combined_rules[uri]["names"].extend(name)
                            else:
                                uri_combined_rules[uri]["names"].append(name)
                        if ignore:
                            uri_combined_rules[uri]["ignore"] = [item.lower() for item in ignore]
                else:
                    # 只有 URI，过滤整个块
                    if is_regex:
                        uri_only_patterns.append(uri)
                    else:
                        uri_only_rules.append(uri)

            # 查找所有 URI 测试块
            filtered_html_parts = []
            pos = 0
            uri_pattern = r'<tr><th class="titlerow bluebg">([^<]+?)&ensp;'

            while True:
                uri_match = re.search(uri_pattern, html_content[pos:])
                if not uri_match:
                    filtered_html_parts.append(html_content[pos:])
                    break

                uri_start = pos + uri_match.start()
                uri = uri_match.group(1).strip()
                block_start = uri_start

                next_uri_match = re.search(uri_pattern, html_content[uri_start + len(uri_match.group(0)):])
                if next_uri_match:
                    block_end = uri_start + len(uri_match.group(0)) + next_uri_match.start()
                else:
                    end_match = re.search(r'</table></body></html>', html_content[block_start:])
                    if end_match:
                        block_end = block_start + end_match.start()
                    else:
                        block_end = len(html_content)

                filtered_html_parts.append(html_content[pos:block_start])
                full_block = html_content[block_start:block_end]

                # 检查 URI 是否匹配任何规则
                matched_rule = None
                matched_by_regex = False

                # 1. 检查精确匹配的 URI 过滤规则
                if uri in uri_only_rules:
                    filtered_uri_count += 1
                    pos = block_end
                    continue  # 跳过此 URI 块
                # 2. 检查精确匹配的 URI 组合规则
                elif uri in uri_combined_rules:
                    matched_rule = uri_combined_rules[uri]
                # 3. 检查正则表达式匹配的 URI 过滤规则
                else:
                    for pattern in uri_only_patterns:
                        if re.search(pattern, uri):
                            filtered_uri_count += 1
                            matched_by_regex = True
                            break
                    if matched_by_regex:
                        pos = block_end
                        continue  # 跳过此 URI 块
                    # 4. 检查正则表达式匹配的 URI 组合规则
                    if not matched_rule:
                        for rule_data in uri_combined_patterns:
                            if re.search(rule_data["pattern"], uri):
                                matched_rule = rule_data
                                matched_by_regex = True
                                break

                # 应用组合规则
                if matched_rule:
                    ignore_types = matched_rule.get("ignore", [])
                    matching_names = matched_rule.get("names", [])

                    # 如果 names 和 ignore 都为空，过滤整个 URI 块
                    if not ignore_types and not matching_names:
                        filtered_uri_count += 1
                        pos = block_end
                        continue

                    # 先过滤 error/warning 块
                    if ignore_types:
                        full_block = self._filter_error_warning_blocks(full_block, ignore_types, result_counter)
                        if "error" in ignore_types:
                            filtered_error_count += 1
                        if "warning" in ignore_types:
                            filtered_warning_count += 1
                        if "not_tested" in ignore_types:
                            filtered_not_tested_count += 1

                    # 再过滤匹配的 Name 行
                    # Interop 属性检查格式:
                    # - <tr><td>Name</td><td>Value</td><td>Expected</td><td>Actual</td><td class="pass center">PASS</td></tr>
                    # - <tr><td>Name</td><td>Value</td><td>Expected</td><td>Actual</td><td class="center">OK</td></tr>
                    if matching_names:
                        table_row_pattern = r'(<tr>(<td>[^<]+</td>)<td[^>]*>.*?</td><td[^>]*>.*?</td><td[^>]*>.*?</td><td class="(?:pass|fail|warn|log)?\s*center"[^>]*>.*?</td></tr>)'

                        def filter_row(row_match):
                            nonlocal filtered_name_count
                            full_row = row_match.group(1)
                            name_cell = row_match.group(2)
                            # 提取属性名，并解码 HTML 实体（支持 # / 等特殊字符）
                            name_match = re.search(r'<td>([^<]+)</td>', name_cell)
                            if name_match:
                                name = html.unescape(name_match.group(1).strip())
                                if name in matching_names:
                                    filtered_name_count += 1
                                    return ""
                            return full_row

                        full_block = re.sub(table_row_pattern, filter_row, full_block, flags=re.DOTALL)
                        if filtered_name_count > 0:
                            print(f"    URI {uri}: 过滤了 {filtered_name_count} 个属性")

                    filtered_html_parts.append(full_block)
                else:
                    filtered_html_parts.append(full_block)

                pos = block_end

            filtered_html = ''.join(filtered_html_parts)

            # 统计结果：从每个 URI 块的标题栏中提取 pass、error、warning、not_tested 的值
            # 标题栏格式：<td class="log">...<b>pass: </b>1</div>...</td>
            # 或者：<td class="log"><div class="fail"><b>error: </b>1</div><div class="warn"><b>warning: </b>1</div></td>

            # 提取所有标题栏中的统计信息
            stats_pattern = r'<td class="log">.*?</td>'
            for match in re.finditer(stats_pattern, filtered_html):
                td_content = match.group(0)

                # 提取 pass 值
                pass_match = re.search(r'<b>pass:\s*</b>\s*(\d+)', td_content)
                if pass_match:
                    result_counter["pass"] += int(pass_match.group(1))

                # 提取 error 值
                error_match = re.search(r'<b>error:\s*</b>\s*(\d+)', td_content)
                if error_match:
                    result_counter["error"] += int(error_match.group(1))

                # 提取 warning 值
                warning_match = re.search(r'<b>warning:\s*</b>\s*(\d+)', td_content)
                if warning_match:
                    result_counter["warning"] += int(warning_match.group(1))

                # 提取 not_tested 值
                not_tested_match = re.search(r'<b>not_tested:\s*</b>\s*(\d+)', td_content)
                if not_tested_match:
                    result_counter["not_tested"] += int(not_tested_match.group(1))

            # fail 值等于 error 值
            result_counter["fail"] = result_counter.get("error", 0)

            # 更新 Results Summary
            summary_pattern = r'<div>(Pass:\s*\d+,\s*Fail:\s*\d+,\s*Warning:\s*\d+,\s*Not Tested:\s*\d+)</div>'
            summary_match = re.search(summary_pattern, filtered_html)
            if summary_match:
                pass_count = result_counter.get("pass", 0)
                fail_count = result_counter.get("fail", 0)
                warning_count = result_counter.get("warning", 0)
                not_tested_count = result_counter.get("not_tested", 0)

                new_summary = f'<div>Pass: {pass_count}, Fail: {fail_count}, Warning: {warning_count}, Not Tested: {not_tested_count}</div>'
                filtered_html = filtered_html[:summary_match.start()] + new_summary + filtered_html[summary_match.end():]
                print(f"  更新 Results Summary: Pass={pass_count}, Fail={fail_count}, Warning={warning_count}, Not Tested={not_tested_count}")

            filter_msg = f"  过滤掉 {filtered_uri_count} 个 URI，{filtered_name_count} 个匹配行"
            if filtered_error_count > 0:
                filter_msg += f"，{filtered_error_count} 个 error 块"
            if filtered_warning_count > 0:
                filter_msg += f"，{filtered_warning_count} 个 warning 块"
            if filtered_not_tested_count > 0:
                filter_msg += f"，{filtered_not_tested_count} 个 not_tested 计数"
            print(filter_msg)

            return filtered_html, dict(result_counter)

        except FileNotFoundError:
            print(f"错误：找不到文件 {file_path}")
            return '', dict(result_counter)
        except Exception as e:
            print(f"解析HTML文件时出错：{str(e)}")
            import traceback
            traceback.print_exc()
            return '', dict(result_counter)

    def get_mapped_args(self) -> dict:
        """返回参数映射字典，将通用参数映射到 interop 特定参数"""
        return {
            "user": "--username",
            "rhost": "--ip"
        }

    def process_txt_files(self, input_dir: str, output_dir: str, result_counter: dict):
        """
        处理并美化TXT格式文件（Interop 特有）
        :param input_dir: 输入目录
        :param output_dir: 输出目录
        :param result_counter: 结果统计计数器
        """
        import os

        txt_files = [os.path.join(input_dir, f) for f in os.listdir(input_dir) if f.startswith('ConformanceLog') and f.endswith('.txt')]

        if not txt_files:
            return

        print(f"\n找到 {len(txt_files)} 个TXT文件")

        for txt_file in txt_files:
            filename = os.path.basename(txt_file)
            print(f"正在处理: {filename}")

            try:
                with open(txt_file, 'r', encoding='utf-8') as f:
                    lines = f.readlines()

                # 更新统计值
                updated_lines = []
                for line in lines:
                    # 查找并更新 Results Summary 行
                    if 'Pass:' in line and 'Fail:' in line and 'Warning:' in line:
                        pass_count = result_counter.get("pass", 0)
                        fail_count = result_counter.get("fail", 0)
                        warning_count = result_counter.get("warning", 0)
                        not_tested_count = result_counter.get("not_tested", 0)

                        # 保留前缀（如 "INFO - "）
                        prefix_match = re.match(r'^(INFO -\s*)', line)
                        prefix = prefix_match.group(1) if prefix_match else ''

                        updated_line = f'{prefix}Pass: {pass_count}, Fail: {fail_count}, Warning: {warning_count}, Not Tested: {not_tested_count}\n'
                        updated_lines.append(updated_line)
                    else:
                        updated_lines.append(line)

                # 写入输出文件
                output_txt_path = os.path.join(output_dir, filename)
                with open(output_txt_path, 'w', encoding='utf-8') as f:
                    f.writelines(updated_lines)
                print(f"  已保存到 {output_txt_path}")

                # 美化输出到 shell
                self._print_txt_summary(updated_lines, result_counter)

            except Exception as e:
                print(f"  处理TXT文件时出错：{str(e)}")

    def build_run_args(self, args) -> list:
        """
        构建 interop 校验器特有的运行参数
        :param args: 参数对象
        :return: 参数列表
        """
        cmd_args = []
        if hasattr(args, 'verbose') and args.verbose:
            cmd_args.extend(["-" + "v" * args.verbose])
        if hasattr(args, 'config') and args.config:
            cmd_args.extend(["--config", args.config])
        if hasattr(args, 'logdir') and args.logdir:
            cmd_args.extend(["--logdir", args.logdir])
        if hasattr(args, 'debugging') and args.debugging:
            cmd_args.append("--debugging")
        if hasattr(args, 'nooemcheck') and args.nooemcheck:
            cmd_args.append("--nooemcheck")
        if hasattr(args, 'no_online_profiles') and args.no_online_profiles:
            cmd_args.append("--no_online_profiles")
        if hasattr(args, 'warnrecommended') and args.warnrecommended:
            cmd_args.append("--warnrecommended")
        if hasattr(args, 'description') and args.description:
            cmd_args.extend(["--description", args.description])
        if hasattr(args, 'forceauth') and args.forceauth:
            cmd_args.append("--forceauth")
        if hasattr(args, 'authtype') and args.authtype:
            cmd_args.extend(["--authtype", args.authtype])
        if hasattr(args, 'token') and args.token:
            cmd_args.extend(["--token", args.token])
        if hasattr(args, 'payload') and args.payload:
            for p in args.payload:
                if isinstance(p, list):
                    cmd_args.extend(["--payload"] + p)
                else:
                    cmd_args.extend(["--payload", p])
        if hasattr(args, 'required_profiles_dir') and args.required_profiles_dir:
            cmd_args.extend(["--required_profiles_dir", args.required_profiles_dir])
        if hasattr(args, 'collectionlimit') and args.collectionlimit:
            cmd_args.extend(["--collectionlimit"] + list(args.collectionlimit))
        if hasattr(args, 'schema') and args.schema:
            cmd_args.extend(["--schema", args.schema])
        if hasattr(args, 'writecheck') and args.writecheck:
            cmd_args.append("--writecheck")
        # 添加 profile 位置参数
        if hasattr(args, 'profile') and args.profile:
            cmd_args.extend(list(args.profile))
        return cmd_args
