from functools import reduce
from pydantic import BaseModel, ValidationError
from termcolor import colored
from source.excel import Excel
from model.common.errordict import error_dict
from .context import DATADIR


class CommonModel(BaseModel):
    # every succesor class has its own properties
    class Config:
        arbitrary_types_allowed = True

    # initialize the model with a list of excels, which includes all nececcery information the model required.
    # output_excel_file is finename for generating new excel. global_dict is the dict generated by globals() from successor's class
    def __init__(self, excels, output_excel_file, global_dict, generate_excel=True):
        # add up all input excels and return the combined one
        *original_excel_objs, new_excel_obj = self._addupExcels(
            excels
        )  # the last one is new_excel_obj, before this are all original_objs
        if output_excel_file:
            data = self._generateExcel(global_dict, new_excel_obj, output_excel_file)
            if generate_excel:
                self.generateExcel(**data)
        else:
            # get the dict as input kwargs. Here globals() is very important. Without it, the KwargsMix cannot find required classes
            kwargs = self._getInput(global_dict, original_excel_objs, new_excel_obj)
            # use pre-assigned referrence value (kwargs) to initialize the model
            try:
                super().__init__(**kwargs)
            except ValidationError as e:
                print(e)
                exit(1)

    # get combined excel throgh add up all input excels.
    def _addupExcels(self, excels):
        excel_objs = list(map(lambda x: Excel(x), excels))
        return [*excel_objs, reduce(lambda a, b: a + b, excel_objs)]

    """
    Prerequisite of below methods
    1. excel_obj is the one that all input excels combined, no sheet has same name
    2. Successor's model is constructed with all sheets/tables, no specific single varialbe
    3. Only InfoSheet and TableList two data structure in the successor's model properties
    """

    def _getInput(self, global_dict, original_excel_objs, new_excel_obj):

        input_kwargs = {}
        # According to the prerequisite described above, all the referrenced scheme are defined in 'definitions'.
        # key is Model name, and after lower(), it is also excel sheet name and the property defined in this class. They are same. This is the requirement.
        for k in self.schema()["definitions"].keys():
            # globals() returns a dict inlcuding all global classes, methods, ....
            klass = global_dict.get(k)
            # use k.lower (it's a sheet name here) to get sheet data
            sheet_data = new_excel_obj.dict.get(k.lower(), None)
            # only two types, one is list of model objects,another is model object
            if not isinstance(sheet_data, list):
                # assign the input kwargs. Here k.lower() is class's property. klass is the model, on which the app create an obj
                try:
                    input_kwargs[k.lower()] = klass(**sheet_data)
                except ValidationError as e:
                    sheet_name = "info-" + k.lower()  # sheet name
                    excel_obj = [
                        excel_obj
                        for excel_obj in original_excel_objs
                        if sheet_name in excel_obj.sheet_names
                    ][0]
                    self.interpretError(excel_obj, sheet_name, e)
            else:
                try:
                    input_kwargs[k.lower()] = [klass(**d) for d in sheet_data]
                except ValidationError as e:
                    table_name = "table-" + k.lower()  # sheet name
                    excel_obj = [
                        excel_obj
                        for excel_obj in original_excel_objs
                        if table_name in excel_obj.table_names
                    ][0]
                    self.interpretError(excel_obj, table_name, e)
        return input_kwargs

    # generate a new excel according to the program class's properties
    def _generateExcel(self, global_dict, new_excel_obj, excel_file):
        sheets = []
        tables = []
        # successo's referrenced class is retored in schema's definition fields with dict format.
        # 获得每一sheet对应class，并获得它的properties list
        classes = self.schema()["definitions"].keys()
        for k in classes:
            # global_dict is a dict inlcuding all global classes, methods, ....
            klass = global_dict.get(
                k
            )  # k here is actually a sheet/table name, and klass is the class related to sheet.
            variables = list(
                klass.__fields__.keys()
            )  # get all variables in the sheet/table
            sheet_table_name = (
                k.lower()
            )  # k.lower is the sheet / table name according to convention
            if "info-" + sheet_table_name in new_excel_obj.sheet_names:
                sheets.append(
                    new_excel_obj.getSheet(sheet_table_name, variables)
                )  # get sheet object
            if "table-" + sheet_table_name in new_excel_obj.table_names:
                tables.append(
                    new_excel_obj.getTable(sheet_table_name, variables)
                )  # get table object
        return {
            "new_excel_obj": new_excel_obj,
            "excel_file": excel_file,
            "sheets": sheets,
            "tables": tables,
        }

    def generateExcel(self, **data):
        # 根据最终更新了variables的sheets和talbes，生成新的excel文件
        data["new_excel_obj"].makeExcel(
            data["excel_file"], sheets=data["sheets"], tables=data["tables"]
        )

    # def interpretError(self,excel_obj,sheet_name,e:ValidationError):
    #     excel_name=excel_obj.excel_name.split('/')[-1] # only get the excel file name and ignore the path
    #     print(colored(f"{excel_name}{error_dict.get('file')}{sheet_name}{error_dict.get('sheet')}{error_dict.get('has error')}，{error_dict.get('detail')}: ",'red'))
    #     for err in e.errors():
    #         for variable in err['loc']:
    #             if sheet_name.startswith('info-'):
    #                 sheet=excel_obj.getSheet(sheet_name[5:]) # remove info-
    #                 info_node=sheet.data.get(variable)
    #                 if info_node !=None:
    #                     print(colored(f"{info_node.description}",'red'),error_dict.get(err['msg']),error_dict.get('has error'))
    #             if sheet_name.startswith('table-'):
    #                 table=excel_obj.getTable(sheet_name[6:])
    #                 title=table.column_titles.get(variable)
    #                 print(colored(f"{title}",'red'),error_dict.get('the_column'),error_dict.get('has error'),error_dict.get(err['msg'],error_dict.get('error')))
    #     print(e.json)

    def interpretError(self, excel_obj, sheet_name, e: ValidationError):
        excel_name = excel_obj.excel_name.split("/")[
            -1
        ]  # only get the excel file name and ignore the path
        print(
            colored(
                f"{excel_name}{error_dict.get('file')}{sheet_name}{error_dict.get('sheet')}{error_dict.get('has error')}，{error_dict.get('detail')}: ",
                "red",
            )
        )
        for err in e.errors():
            for variable in err["loc"]:
                if sheet_name.startswith("info-"):
                    sheet = excel_obj.getSheet(sheet_name[5:])  # remove info-
                    info_node = sheet.data.get(variable)
                    if info_node != None:
                        print(colored(f"{info_node.description}", "red"), err["msg"])
                if sheet_name.startswith("table-"):
                    table = excel_obj.getTable(sheet_name[6:])
                    title = table.column_titles.get(variable)
                    print(
                        colored(f"{title}", "red"), "the_column has error", err["msg"]
                    )
        print(e)

    def getExcels(self, templates=[]):
        excels = []
        import os

        for template in templates:
            excel_file = os.path.abspath(os.path.join(DATADIR, template))
            excels.append(excel_file)
        return excels
