#!/usr/bin/env python
# coding: utf-8
# Copyright (c) 2024 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 os
import re
import json
import shutil
from collections import defaultdict
from enum import Enum
from openpyxl import Workbook
from openpyxl.styles import PatternFill, Alignment, Side, Border
from datetime import datetime


ERRORLOG_PATH = 'interface_config/redfish/schema_check/error_log.xlsx'
ODATA_INFO = ['@odata.context', '@odata.id', '@odata.type']
NUM2LET_DIA = 64


def get_abspath_under_rackmount(path):
    return os.path.join(os.getcwd(), path)


def mk_new_dir(path):
    if os.path.exists(path):
        shutil.rmtree(path)
    os.makedirs(path)


def load_json(path):
    if os.path.exists(path):
        try:
            with open(path, 'r', encoding='utf-8') as fp:
                fp_json = json.load(fp)
                return fp_json
        except json.JSONDecodeError as e:
                print(f"Error decoding JSON: {e}")
        except Exception as e:
                print(f"An error occurred when load {path}: {e}")
    return {}
    

def dump_json(path, json_dict):
    with open(path, 'w', encoding='utf-8') as fp:
        fp.write(json.dumps(json_dict, indent=4, ensure_ascii=False))
        return True
    return False


def del_json(path):
    if os.path.exists(path):
        os.remove(path)


def is_local_ref(ref):
    pattern = r'^(#\/)|(oem\/\w+\.json#\/)'
    ret = re.search(pattern, ref)
    if ret:
        return True
    return False


def get_rscname(filename):
    pattern = re.compile(r'\.v[0-9]+_[0-9]+_[0-9]+\.json')
    ret = re.match(pattern, filename) or re.match(r'\.json', filename) or None
    if ret:
        return filename[:ret.span()[0]]
    else:
        return None

def get_version(filename):
    pattern = r'\.v(\d+)_(\d+)_(\d+)\.json'
    match = re.search(pattern, filename)
    if match:
        # 如果匹配成功，使用group方法获取匹配的子串
        return 'v' + '_'.join(match.groups())
    else:
        # 如果没有找到匹配，返回None，根据具体需求可更改
        return None


def get_prop_list_from_ref(ref):
    prop_list = []
    # 依次匹配/和/之间的字符串并提取到prop_list
    if not ref.endswith('/'):
        ref = ref + '/'
    ret = re.search(r'/(.+?)/', ref)
    while ret is not None:
        index = ret.span()
        prop_list.append(ref[(index[0]+1):(index[1]-1)])
        ref = ref[(index[1]-1):]
        ret = re.search(r'/(.+?)/', ref)
    return prop_list


def get_prop_from_ref(input_json, ref):
    prop_list = get_prop_list_from_ref(ref)
    prop = input_json
    for p in prop_list:
        if p in prop:
            prop = prop[p]
        else:
            return {}
    return prop


def get_rsc_name(filename):
    pattern = r'(\.v(\d+\_){2}\d)?\.json'
    ret = re.search(pattern, filename)
    if ret is not None:
        index = ret.span()
        return filename[:index[0]]
    return ''


def get_schema_from_odata_type(odata_type):
    pattern = r'^#(\w+)(?:\.v\d+_\d+_\d+)?\.(\w+)$'
    ret = re.search(pattern, odata_type)
    # 映射器会检查，一般不会失配
    if ret:
        p = re.split('#|\.', odata_type)
        rsc_name = '.'.join(p[1:-1])
        prop_name = p[-1]
        return rsc_name.lower(), prop_name
    else:
        return None
    

def get_prop_from_local_ref(input_json, ref, schema_dir):
    if ref.startswith('#'):
        return get_prop_from_ref(input_json, ref), None
    path_list = re.split('/', ref)
    file_loc = 0
    for file_loc in range(len(path_list)):
        if path_list[file_loc].endswith('#'):
            break
        schema_dir = '/'.join([schema_dir, path_list[file_loc]])
    schema_dir = '/'.join([schema_dir, path_list[file_loc][:-1]])
    input_json = load_json(schema_dir)
    return get_prop_from_ref(input_json, '/'.join(path_list[file_loc:])), schema_dir


class ERRORLOG_COL(Enum):
    MAPPER_FILE = 1
    SCHEMA_FILE = 2
    RSC_NAME = 3
    VERSION = 4
    RSC_URI = 5
    RSC_TYPE = 6
    PROP = 7
    ERROR_INFO = 8
    ERROR_INFLUENCE = 9
    SUGGESTION = 10
    CHECK_TIME = 11


class ErrorlogPrinter:
    def __init__(self):
        self.errorlog_path = get_abspath_under_rackmount(ERRORLOG_PATH)
        self.error_ruleinfo_table = defaultdict(self.default_error)
        self.error_ruleinfo_table['1-2'] = '自定义属性语法校验失败'
        self.error_ruleinfo_table['2-1'] = '资源定义完整性检查失败'
        self.error_ruleinfo_table['2-2'] = '资源方法有效性检查失败'
        self.error_ruleinfo_table['2-3'] = '属性定义完整性检查失败'
        self.error_ruleinfo_table['2-4'] = '属性类型定义一致性检查失败'
        self.error_ruleinfo_table['2-5'] = 'POST请求的requiredOnCreate约束检查失败'
        self.error_ruleinfo_table['2-6'] = 'GET请求的required约束检查失败'
        self.error_ruleinfo_table['2-7'] = 'PATCH请求的readonly约束检查失败'
        self.row_num = 0
        self.check_time = datetime.now()
        self.check_time = self.check_time.strftime("%Y-%m-%d %H:%M:%S")

    def default_error(self):
        return '未定义的Rule序号'
    
    def get_error_list(self, is_success, error_list):
        if is_success:
            return
        for e in error_list:
            self.print_errorlog(e[0], *e[1:])
    
    def print_errorlog(self, rule_id, *error_info):
        self.print_errorlog2file(rule_id, *error_info)
        self.print_errorlog2tmn(rule_id, *error_info)

    def init_errorlog_file(self):
        if os.path.exists(get_abspath_under_rackmount(ERRORLOG_PATH)):
            os.remove(get_abspath_under_rackmount(ERRORLOG_PATH))
        self.wb = Workbook()
        self.ws = self.wb.active
        self.ws.title = "Schema检查分析报告"
        self.row_num = self.row_num + 1
        self.ws.row_dimensions[self.row_num].height = 20
        self.ws.cell(row=self.row_num, column=ERRORLOG_COL.MAPPER_FILE.value).value = "映射器文件"
        self.ws.column_dimensions[chr(ERRORLOG_COL.MAPPER_FILE.value + NUM2LET_DIA)].width = 40
        self.ws.cell(row=self.row_num, column=ERRORLOG_COL.SCHEMA_FILE.value).value = "Schema文件"
        self.ws.column_dimensions[chr(ERRORLOG_COL.SCHEMA_FILE.value + NUM2LET_DIA)].width = 40
        self.ws.cell(row=self.row_num, column=ERRORLOG_COL.RSC_NAME.value).value = "资源对象名称"
        self.ws.column_dimensions[chr(ERRORLOG_COL.RSC_NAME.value + NUM2LET_DIA)].width = 30
        self.ws.cell(row=self.row_num, column=ERRORLOG_COL.VERSION.value).value = "版本"
        self.ws.column_dimensions[chr(ERRORLOG_COL.VERSION.value + NUM2LET_DIA)].width = 9
        self.ws.cell(row=self.row_num, column=ERRORLOG_COL.RSC_URI.value).value = "资源uri"
        self.ws.column_dimensions[chr(ERRORLOG_COL.RSC_URI.value + NUM2LET_DIA)].width = 30
        self.ws.cell(row=self.row_num, column=ERRORLOG_COL.RSC_TYPE.value).value = "资源方法"
        self.ws.column_dimensions[chr(ERRORLOG_COL.RSC_TYPE.value + NUM2LET_DIA)].width = 10
        self.ws.cell(row=self.row_num, column=ERRORLOG_COL.PROP.value).value = "属性"
        self.ws.column_dimensions[chr(ERRORLOG_COL.PROP.value + NUM2LET_DIA)].width = 25
        self.ws.cell(row=self.row_num, column=ERRORLOG_COL.ERROR_INFO.value).value = "错误提示"
        self.ws.column_dimensions[chr(ERRORLOG_COL.ERROR_INFO.value + NUM2LET_DIA)].width = 40
        self.ws.cell(row=self.row_num, column=ERRORLOG_COL.ERROR_INFLUENCE.value).value = "错误影响"
        self.ws.cell(row=self.row_num, column=ERRORLOG_COL.SUGGESTION.value).value = "处理建议"
        self.ws.cell(row=self.row_num, column=ERRORLOG_COL.CHECK_TIME.value).value = "检查时间"
        self.ws.column_dimensions[chr(ERRORLOG_COL.CHECK_TIME.value + NUM2LET_DIA)].width = 20
        self.wb.save(ERRORLOG_PATH)

    def print_errorlog2file(self, rule_id, *error_info):
        if self.row_num == 0:
            self.init_errorlog_file()
        error_ruleinfo = self.error_ruleinfo_table[rule_id]
        self.row_num = self.row_num + 1
        self.ws.row_dimensions[self.row_num].height = 20
        self.ws.cell(row=self.row_num, column=ERRORLOG_COL.MAPPER_FILE.value).value = error_info[0]
        self.ws.cell(row=self.row_num, column=ERRORLOG_COL.SCHEMA_FILE.value).value = error_info[1]
        self.ws.cell(row=self.row_num, column=ERRORLOG_COL.RSC_URI.value).value = error_info[3]
        self.ws.cell(row=self.row_num, column=ERRORLOG_COL.RSC_TYPE.value).value = error_info[4]
        self.ws.cell(row=self.row_num, column=ERRORLOG_COL.PROP.value).value = error_info[5]
        self.ws.cell(row=self.row_num, column=ERRORLOG_COL.RSC_NAME.value).value = error_info[6]
        self.ws.cell(row=self.row_num, column=ERRORLOG_COL.VERSION.value).value = error_info[7]
        self.ws.cell(row=self.row_num, column=ERRORLOG_COL.ERROR_INFO.value).value = '[Rule {}]: {}'.format(rule_id, error_ruleinfo)
        self.ws.cell(row=self.row_num, column=ERRORLOG_COL.CHECK_TIME.value).value = self.check_time
        self.wb.save(ERRORLOG_PATH)

    def print_errorlog2tmn(self, rule_id, *error_info):
        error_ruleinfo = self.error_ruleinfo_table[rule_id]
        print('=============constraint errors found in file:')
        print('映射器文件: {}'.format(error_info[0]))
        print('Schema文件: {}'.format(error_info[1]))
        print('[Rule {}]: {}, 映射器错误值: {}, 错误位置: URI: {}, Type: {}, 位置: {}'.format(
            rule_id, 
            error_ruleinfo,
            error_info[2], 
            error_info[3], 
            error_info[4],
            error_info[5]))
        
ep = ErrorlogPrinter()