#!/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 argparse
import json
import os
import subprocess
import sys
from collections import defaultdict
from typing import Optional, Union, List

from validators import create_validator


def install_package(package_name, version=None):
    """
    执行pip install命令安装指定包
    :param package_name: 要安装的包名
    :param version: 要安装的包版本
    """
    try:
        # 创建验证器实例获取标准包名
        validator = create_validator(package_name)
        normalized_name = validator.package_name
    except ValueError as e:
        print(f"错误: {e}")
        return

    try:
        print(f"开始安装包: {normalized_name}")
        if version:
            cmd = [sys.executable, "-m", "pip", "install", f"{normalized_name}=={version}"]
        else:
            cmd = [sys.executable, "-m", "pip", "install", normalized_name]
        result = subprocess.run(
            cmd,
            check=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True
        )
        print(f"包 {normalized_name} 安装成功！")
        print("安装输出：")
        print(result.stdout)
    except subprocess.CalledProcessError as e:
        print(f"包 {normalized_name} 安装失败！错误信息：")
        print(e.stdout)
        return
    except Exception as e:
        print(f"执行安装命令时发生未知错误：{str(e)}")
        return


def _load_white_list(white_list_path: str, validator_type: str) -> Union[dict, list]:
    """
    加载白名单文件
    :param white_list_path: 白名单文件路径
    :param validator_type: 验证器类型
    :return: 白名单字典或列表（Interop 返回列表）
    """
    try:
        with open(white_list_path, 'r', encoding='utf-8') as f:
            exclude_data = json.load(f)

        validator = create_validator(validator_type)
        normalized_type = validator.package_name

        # Interop 使用不同的格式（Name + URI 列表）
        if validator_type == "interop":
            white_list = []
            for rule in exclude_data.get("ExcludeRules", []):
                if rule.get("Validator") == normalized_type:
                    white_list = rule.get("ExcludedAssertions", [])
            print(f"已加载排除列表，过滤 {len(white_list)} 条规则")
            return white_list

        # Service 使用 Name 字段格式 或 Ignore 属性
        if validator_type == "service":
            white_list = {}
            for rule in exclude_data.get("ExcludeRules", []):
                if rule.get("Validator") == normalized_type:
                    for item in rule.get("ExcludedAssertions", []):
                        # Service 格式: {Name, URI} 或 {URI} 或 {URI, Ignore} 或 {URI, Name, Ignore}
                        # 使用 URI 作为 key
                        uri = item.get("URI", "")
                        name = item.get("Name", "")
                        ignore = item.get("Ignore")

                        if not uri:
                            continue

                        # 构建 white_list[uri] 的规则
                        if uri not in white_list:
                            white_list[uri] = {}

                        rule_entry = white_list[uri]

                        # 如果是列表格式（来自只有 Name 的情况），转换为字典
                        if isinstance(rule_entry, list):
                            rule_entry = {"Names": rule_entry, "Ignore": []}
                            white_list[uri] = rule_entry

                        # 确保是字典格式
                        if isinstance(rule_entry, dict):
                            if "Names" not in rule_entry:
                                rule_entry["Names"] = []
                            if "Ignore" not in rule_entry:
                                rule_entry["Ignore"] = []

                            # 添加 Name（支持字符串或列表）
                            if name:
                                if isinstance(name, list):
                                    rule_entry["Names"].extend(name)
                                else:
                                    rule_entry["Names"].append(name)

                            # 添加 Ignore（支持 None、空列表和非空列表）
                            if ignore is not None:
                                rule_entry["Ignore"] = ignore

                        # 如果最终 Names 为空且 Ignore 为空列表，设置为空列表表示过滤整个 URI
                        # 注意：这里只检查 Ignore 是否为空列表，不包括 None（因为可能是从其他地方传入的）
                        names = rule_entry.get("Names", [])
                        ignore = rule_entry.get("Ignore", [])
                        if not names and (not ignore or ignore == []):
                            white_list[uri] = []

            print(f"已加载排除列表，过滤 {len(white_list)} 个URI")
            return white_list

        # Protocol 使用 AssertionName 格式
        white_list = {}
        for rule in exclude_data.get("ExcludeRules", []):
            if rule.get("Validator") == normalized_type:
                for item in rule.get("ExcludedAssertions", []):
                    assertion = item.get("AssertionName")
                    if assertion not in white_list:
                        white_list[assertion] = []
                    if "URI" in item or "Method" in item:
                        white_list[assertion].append({
                            "URI": item.get("URI", ""),
                            "Method": item.get("Method", "")
                        })
        print(f"已加载排除列表，过滤 {len(white_list)} 个Assertion")
        return white_list

    except FileNotFoundError:
        print(f"警告：未找到排除列表文件 {white_list_path}，不进行过滤")
        return {} if validator_type != "interop" else []
    except json.JSONDecodeError:
        print(f"警告：排除列表文件 {white_list_path} 格式错误，不进行过滤")
        return {} if validator_type != "interop" else []
    except Exception as e:
        print(f"警告：读取排除列表文件时出错：{str(e)}，不进行过滤")
        return {} if validator_type != "interop" else []


def _process_tsv_files(validator, input_dir: str, output_dir: str, white_list: dict) -> dict:
    """
    处理TSV格式文件
    :param validator: 校验器实例
    :param input_dir: 输入目录
    :param output_dir: 输出目录
    :param white_list: 白名单
    :return: 结果计数器
    """
    total_result_counter = defaultdict(int)
    tsv_files = [os.path.join(input_dir, f) for f in os.listdir(input_dir) if f.endswith('.tsv')]

    if not tsv_files:
        print(f"错误：在目录 {input_dir} 中未找到TSV文件")
        return {}

    print(f"找到 {len(tsv_files)} 个TSV文件")

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

        filtered_lines, header_line, result_counter = validator.parse_tsv_excluded(tsv_file, white_list)

        for result, count in result_counter.items():
            total_result_counter[result] += count

        output_tsv_path = os.path.join(output_dir, filename)
        with open(output_tsv_path, 'w', encoding='utf-8', newline='') as f:
            if filtered_lines:
                f.write(header_line + '\n')
                f.write('\n'.join(filtered_lines))
                print(f"  已写入 {len(filtered_lines)} 行到 {output_tsv_path}")
            else:
                f.write(header_line + '\n')

    return dict(total_result_counter)


def _process_html_files(validator, input_dir: str, output_dir: str, white_list: dict) -> dict:
    """
    处理HTML格式文件
    :param validator: 校验器实例
    :param input_dir: 输入目录
    :param output_dir: 输出目录
    :param white_list: 白名单
    :return: 结果计数器
    """
    total_result_counter = defaultdict(int)
    html_files = [os.path.join(input_dir, f) for f in os.listdir(input_dir) if f.endswith('.html')]

    if not html_files:
        print(f"错误：在目录 {input_dir} 中未找到HTML文件")
        return {}

    print(f"找到 {len(html_files)} 个HTML文件")

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

        filtered_html, result_counter = validator.parse_html_excluded(html_file, white_list)

        for result, count in result_counter.items():
            total_result_counter[result] += count

        output_html_path = os.path.join(output_dir, filename)
        with open(output_html_path, 'w', encoding='utf-8') as f:
            f.write(filtered_html)
        print(f"  已保存到 {output_html_path}")

    return dict(total_result_counter)


def analyse_result(validator_type: str, input_dir: str, format_type: str = "tsv",
                   output_dir: str = "./output", white_list_path: Optional[str] = None):
    """
    分析Redfish检查器校验结果
    :param validator_type: 检查器类型
    :param input_dir: 输入目录路径
    :param format_type: 输入文件格式
    :param output_dir: 输出目录路径
    :param white_list: 白名单文件路径
    """
    try:
        validator = create_validator(validator_type)
    except ValueError as e:
        print(f"错误: {e}")
        return

    # 校验格式是否被验证器支持
    format_lower = format_type.lower()
    supported_formats = [f.lower() for f in validator.supported_formats]
    if format_lower not in supported_formats:
        print(f"错误：验证器 {validator_type} 不支持格式 {format_type}，仅支持 {', '.join(validator.supported_formats)}")
        return

    white_list = _load_white_list(white_list_path, validator_type) if white_list_path else {}
    os.makedirs(output_dir, exist_ok=True)

    total_result_counter = (
        _process_tsv_files(validator, input_dir, output_dir, white_list) if format_lower == "tsv"
        else _process_html_files(validator, input_dir, output_dir, white_list)
    )

    if not total_result_counter:
        return

    # 处理并美化TXT文件（Interop 和 Service）
    if validator.name in ("interop", "service"):
        validator.process_txt_files(input_dir, output_dir, total_result_counter)

    # Protocol 特有的 results.json 处理
    if validator.name == "protocol":
        validator.update_results_json(input_dir, output_dir, total_result_counter)
        validator.print_results_summary(os.path.join(output_dir, 'results.json'))

    print(f"\n分析完成！结果已保存到 {output_dir} 目录")
    return


def run_validator(validator_type: str, args, extra_args: Optional[List[str]] = None):
    """
    执行指定的 Redfish 校验器命令
    :param validator_type: 校验器类型
    :param args: 参数对象
    :param extra_args: 额外的透传参数
    """
    try:
        validator = create_validator(validator_type)
    except ValueError as e:
        print(f"错误: {e}")
        return

    cmd = validator.get_run_args()

    # 获取参数映射
    arg_mapping = validator.get_mapped_args()
    user_arg = arg_mapping.get("user", "--user")
    rhost_arg = arg_mapping.get("rhost", "--rhost")

    cmd.extend([user_arg, args.user, "--password", args.password, rhost_arg, args.rhost])

    # 添加校验器特有的参数
    cmd.extend(validator.build_run_args(args))

    # 透传额外参数
    if extra_args:
        cmd.extend(extra_args)

    try:
        print(f"执行校验器: {validator.package_name}")
        print(f"命令: {' '.join(cmd)}")
        result = subprocess.run(cmd)
        print(f"校验完成！")
    except subprocess.CalledProcessError as e:
        print(f"校验失败，返回码: {e.returncode}")
        return
    except FileNotFoundError:
        print(f"错误：未找到校验器 {validator.package_name}，请先使用 deploy 命令安装")
        return
    except Exception as e:
        print(f"执行校验器时出错：{str(e)}")
        return


def convert_file(input_path: str, output_format: str, output_path: Optional[str] = None):
    """
    将TSV文件转换为CSV或XLSX格式
    :param input_path: 输入TSV文件路径
    :param output_format: 输出格式（csv或xlsx）
    :param output_path: 输出文件路径（可选）
    """
    if not os.path.exists(input_path):
        print(f"错误：文件不存在 {input_path}")
        return

    if not input_path.endswith('.tsv'):
        print(f"错误：仅支持TSV格式输入")
        return

    output_format = output_format.lower()
    if output_format not in ["csv", "xlsx"]:
        print(f"错误：不支持的格式 {output_format}，仅支持 csv 或 xlsx")
        return

    # 生成输出路径
    if output_path is None:
        base_name = os.path.splitext(os.path.basename(input_path))[0]
        output_path = f"{base_name}.{output_format}"

    # 读取TSV文件
    try:
        with open(input_path, 'r', encoding='utf-8') as f:
            lines = f.readlines()
    except Exception as e:
        print(f"错误：读取文件失败 - {str(e)}")
        return

    if not lines:
        print("错误：TSV文件为空")
        return

    # 解析TSV数据
    rows = [line.strip().split('\t') for line in lines]

    if output_format == "csv":
        import csv
        try:
            with open(output_path, 'w', encoding='utf-8', newline='') as f:
                writer = csv.writer(f)
                writer.writerows(rows)
            print(f"已转换: {input_path} -> {output_path}")
        except Exception as e:
            print(f"错误：写入CSV失败 - {str(e)}")
            return

    elif output_format == "xlsx":
        try:
            import openpyxl
        except ImportError:
            print("错误：需要安装 openpyxl 库，请运行: pip install openpyxl")
            return

        try:
            wb = openpyxl.Workbook()
            ws = wb.active
            for row_data in rows:
                ws.append(row_data)
            wb.save(output_path)
            print(f"已转换: {input_path} -> {output_path}")
        except Exception as e:
            print(f"错误：写入XLSX失败 - {str(e)}")
            return


def main():
    parser = argparse.ArgumentParser(
        description="Redfish相关工具部署脚本",
        prog="redfish_validator.py"
    )

    subparsers = parser.add_subparsers(dest="command", help="可用子命令")

    # deploy 子命令
    deploy_parser = subparsers.add_parser("deploy", help="部署指定的Redfish检查器相关包")
    deploy_parser.add_argument(
        "-t", "--type",
        required=True,
        help="需要安装的包类型（支持protocol/service/interop或完整包名）"
    )
    deploy_parser.add_argument(
        "-v", "--version",
        type=str,
        help="需要安装的包版本（如1.3.0）"
    )

    # analyze 子命令
    analyse_parser = subparsers.add_parser("analyze", help="分析Redfish检查器校验结果")
    analyse_parser.add_argument(
        "-t", "--type",
        required=True,
        help="Redfish检查器类型（protocol/service/interop或完整包名）"
    )
    analyse_parser.add_argument(
        "-i", "--input",
        required=True,
        type=str,
        help="校验结果输入路径"
    )
    analyse_parser.add_argument(
        "-f", "--format",
        type=str,
        default="tsv",
        help="输入文件的格式，默认为tsv"
    )
    analyse_parser.add_argument(
        "-o", "--output",
        type=str,
        default="./output",
        help="分析结果输出路径，默认为./output"
    )
    analyse_parser.add_argument(
        "-e", "--exclude",
        type=str,
        help="过滤白名单文件路径"
    )

    # convert 子命令
    convert_parser = subparsers.add_parser("convert", help="将TSV文件转换为CSV或XLSX格式")
    convert_parser.add_argument(
        "-i", "--input",
        required=True,
        type=str,
        help="输入TSV文件路径"
    )
    convert_parser.add_argument(
        "-f", "--format",
        required=True,
        type=str,
        choices=["csv", "xlsx"],
        help="输出格式（csv或xlsx）"
    )
    convert_parser.add_argument(
        "-o", "--output",
        type=str,
        help="输出文件路径（可选，默认为输入文件同名但扩展名不同）"
    )

    # validate 子命令
    validate_parser = subparsers.add_parser("validate", help="执行Redfish校验器")
    validate_parser.add_argument(
        "-t", "--type",
        required=True,
        help="校验器类型（protocol/service/interop）"
    )
    # 通用认证参数
    validate_parser.add_argument("-u", "--user", "--username", dest="user", required=True, help="认证用户名")
    validate_parser.add_argument("-p", "--password", required=True, help="认证密码")
    validate_parser.add_argument("-r", "--rhost", "--ip", dest="rhost", required=True, help="Redfish服务地址（需包含协议）")
    # interop profile 参数（位置参数）
    validate_parser.add_argument("profile", nargs="*", help="Interop profile 文件路径（interop）")
    # protocol 特有参数
    validate_parser.add_argument("--log-level", choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], help="日志级别（protocol）")
    validate_parser.add_argument("--report-dir", help="生成报告文件的目录（protocol）")
    validate_parser.add_argument("--report-type", choices=["html", "tsv", "both"], help="报告类型: html, tsv 或 both（protocol）")
    validate_parser.add_argument("--avoid-http-redirect", action="store_true", help="避免为不支持HTTP的服务生成HTTP重定向（protocol）")
    validate_parser.add_argument("--no-cert-check", action="store_true", help="禁用主机SSL证书验证（protocol）")
    validate_parser.add_argument("--ca-bundle", help="包含受信任CA的文件或目录（protocol）")
    # interop/service 常用参数
    validate_parser.add_argument("-v", "--verbose", action="count", default=0, help="详细输出级别（interop/service）")
    validate_parser.add_argument("-c", "--config", help="配置文件路径（interop/service）")
    validate_parser.add_argument("--logdir", help="日志文件目录（interop/service，默认: \"./logs\"）")
    validate_parser.add_argument("--debugging", action="store_true", help="输出调试日志（interop/service）")
    validate_parser.add_argument("--nooemcheck", action="store_true", help="不检查OEM项（interop/service）")
    validate_parser.add_argument("--no-online-profiles", "--no_online_profiles", dest="no_online_profiles", action="store_true", help="不自动获取在线配置文件（interop）")
    validate_parser.add_argument("--warnrecommended", action="store_true", help="对推荐项警告（interop）")
    # interop 特有参数
    validate_parser.add_argument("--description", help="系统描述，用于标识日志（interop）")
    validate_parser.add_argument("--forceauth", action="store_true", help="在非安全连接上强制认证（interop）")
    validate_parser.add_argument("--authtype", choices=["None", "Basic", "Session", "Token"], help="授权类型: None, Basic, Session 或 Token（interop）")
    validate_parser.add_argument("--token", help="Token，当 authtype 为 Token 时使用（interop）")
    validate_parser.add_argument("--payload", action="append", nargs=2, metavar=("MODE", "VALUE"), help="验证负载模式，格式: --payload MODE VALUE（interop，可多次使用）")
    validate_parser.add_argument("--required-profiles-dir", help="required profiles 根目录（interop）")
    validate_parser.add_argument("--collectionlimit", nargs="+", help="应用集合限制，格式: RESOURCE1 COUNT1 RESOURCE2 COUNT2...（interop/service）")
    validate_parser.add_argument("--schema", help="Schema 文件（interop）")
    validate_parser.add_argument("--writecheck", action="store_true", help="允许 WriteRequirement 检查（interop）")
    # service 特有参数
    validate_parser.add_argument("--ext-http-proxy", help="访问外部站点的 HTTP 代理 URL（service）")
    validate_parser.add_argument("--ext-https-proxy", help="访问外部站点的 HTTPS 代理 URL（service）")
    validate_parser.add_argument("--serv-http-proxy", help="访问服务的 HTTP 代理 URL（service）")
    validate_parser.add_argument("--serv-https-proxy", help="访问服务的 HTTPS 代理 URL（service）")
    validate_parser.add_argument("--uricheck", action="store_true", help="允许对低于 RedfishVersion 1.6.0 的服务进行 URI 检查（service）")
    validate_parser.add_argument("--schema-directory", help="本地 Schema 文件目录（service）")
    validate_parser.add_argument("--mockup", help="启用本地 mockup 资源（service）")
    validate_parser.add_argument("--requesttimeout", type=int, help="HTTP 请求响应超时时间（秒）（service）")
    validate_parser.add_argument("--requestattempts", type=int, help="HTTP 请求失败后的重试次数（service）")

    args, extra_args = parser.parse_known_args()

    if args.command == "deploy":
        install_package(args.type, args.version)
    elif args.command == "analyze":
        analyse_result(args.type, args.input, args.format, args.output, args.exclude)
    elif args.command == "convert":
        convert_file(args.input, args.format, args.output)
    elif args.command == "validate":
        run_validator(
            validator_type=args.type,
            args=args,
            extra_args=extra_args
        )
    else:
        parser.print_help()
        sys.exit(1)


if __name__ == "__main__":
    main()
