import datetime
import logging
# from difflib import get_close_matches
from pathlib import Path
from typing import Final

import arcpy
import attrs
import pandas as pd
# noinspection PyProtectedMember
# from arcpy._mp import Map as APRXMap, Layer as APRXLayer, Table as APRXTable  # For type hinting
# from arcpy.mp import ArcGISProject

from ...lib.gdbsession import GPMessenger
from ...lib.misc import params_to_dict, unwrap, unwrap_to_dict, get_closest_match_insensitive, qquote
from ...lib.msag import MSAGComparatorFieldMap, MSAGIssue
# from ...lib.msag import NG911MSAGComparator, MSAGFieldMap, TNFieldMap  #### RE-ENABLE WHEN TN COMPARISON IS COMPLETE ####
from ...lib.msag import NG911MSAGComparator, MSAGFieldMap  #### REMOVE WHEN TN COMPARISON IS COMPLETE ####


_logger = logging.getLogger(__name__)
_NULL_STRING: Final[str] = "<Null>"


# noinspection PyMethodMayBeStatic
class MSAGNG911Comparison:
    def __init__(self):
        """Define the tool (tool name is the name of the class)."""
        #### RE-ENABLE WHEN TN COMPARISON IS COMPLETE ####
        # self.label = f"MSAG/TN-NG911 Comparison"
        # self.description = f"MSAG/TN-NG911 Comparison"
        #### ---------------------------------------- ####
        #### REMOVE WHEN TN COMPARISON IS COMPLETE ####
        self.label = f"MSAG-NG911 Comparison"
        self.description = f"MSAG-NG911 Comparison"
        #### ------------------------------------- ####
        self.canRunInBackground = False
        self.category = "4 - MSAG"

    def getParameterInfo(self):
        """Define parameter definitions"""
        params = [
            arcpy.Parameter(
                displayName="NG911 Geodatabase",
                name="gdb",
                datatype="DEWorkspace",
                parameterType="Required",
                direction="Input"
            ),
            arcpy.Parameter(
                displayName="MSAG Spreadsheet",
                name="msag",
                datatype="DEFile",
                parameterType="Optional",
                direction="Input"
            ),
            msag_fm := arcpy.Parameter(
                displayName="MSAG Field Map",
                name="msag_field_map",
                datatype="GPValueTable",
                parameterType="Optional",
                direction="Input"
            ),
            #### RE-ENABLE WHEN TN COMPARISON IS COMPLETE ####
            # arcpy.Parameter(
            #     displayName="Telephone Spreadsheet",
            #     name="tn",
            #     datatype="DEFile",
            #     parameterType="Optional",
            #     direction="Input"
            # ),
            # tn_fm := arcpy.Parameter(
            #     displayName="TN Field Map",
            #     name="tn_field_map",
            #     datatype="GPValueTable",
            #     parameterType="Optional",
            #     direction="Input"
            # ),
            #### ---------------------------------------- ####
            # arcpy.Parameter(
            #     displayName="Create Issue Table Relation(s)",
            #     name="create_relations",
            #     datatype="GPBoolean",
            #     parameterType="Optional",
            #     direction="Input"
            # ),
            arcpy.Parameter(
                displayName="Analysis Output Path",
                name="output_path",
                datatype="DEWorkspace",
                parameterType="Required",
                direction="Output"
            ),
            arcpy.Parameter(
                displayName="Output MSAG Issue Table",
                name="msag_issue_table",
                datatype="DETable",
                parameterType="Derived",
                direction="Output"
            ),
            #### RE-ENABLE WHEN TN COMPARISON IS COMPLETE ####
            # arcpy.Parameter(
            #     displayName="Output TN Issue Table",
            #     name="tn_issue_table",
            #     datatype="DETable",
            #     parameterType="Derived",
            #     direction="Output"
            # ),
            #### ---------------------------------------- ####
        ]

        msag_fm.columns = [["GPString", "MSAG Field Type", "ReadOnly"], ["GPString", "Spreadsheet Field"]]
        msag_fm.filters[1].type = "ValueList"
        msag_fm.filters[1].list = [_NULL_STRING]
        msag_fm.values = [[f, _NULL_STRING] for f in MSAGFieldMap.get_display_names()]

        #### RE-ENABLE WHEN TN COMPARISON IS COMPLETE ####
        # tn_fm.columns = [["GPString", "TN Field Type", "ReadOnly"], ["GPString", "Spreadsheet Field"]]
        # tn_fm.filters[1].type = "ValueList"
        # tn_fm.filters[1].list = [_NULL_STRING]
        # tn_fm.values = [[f, _NULL_STRING] for f in TNFieldMap.get_display_names()]
        #### ---------------------------------------- ####

        return params

    def isLicensed(self):
        """Set whether tool is licensed to execute."""
        return True

    def updateParameters(self, parameters):
        """Modify the values and properties of parameters before internal
        validation is performed.  This method is called whenever a parameter
        has been changed."""
        param_dict = params_to_dict(parameters)

        param_dict["msag_field_map"].enabled = bool(unwrap(param_dict["msag"]))
        if param_dict["msag"].altered and not param_dict["msag"].hasBeenValidated:
            # Set field filter and attempt to autofill
            fm = param_dict["msag_field_map"]
            xlf = pd.ExcelFile(unwrap(param_dict["msag"]))  # Open Excel file
            excel_column_names = [cell.value for cell in next(xlf.book.worksheets[0].rows)]  # Get text in first row of cells; presumably column names
            fm.filters[1].list = [_NULL_STRING, *excel_column_names]  # Pick up around here
            display_names = [row[0] for row in fm.values]
            autofill_column_names = {display_name: closest_col for display_name in display_names if (closest_col := get_closest_match_insensitive(display_name, excel_column_names, 0.75))}
            fm.values = [[name, autofill_column_names.get(name, _NULL_STRING)] for name in display_names]

        #### RE-ENABLE WHEN TN COMPARISON IS COMPLETE ####
        # param_dict["tn_field_map"].enabled = bool(unwrap(param_dict["tn"]))
        # if param_dict["tn"].altered and not param_dict["tn"].hasBeenValidated:
        #     # Set field filter and attempt to autofill
        #     fm = param_dict["tn_field_map"]
        #     xlf = pd.ExcelFile(unwrap(param_dict["tn"]))  # Open Excel file
        #     excel_column_names = [cell.value for cell in next(xlf.book.worksheets[0].rows)]  # Get text in first row of cells; presumably column names
        #     fm.filters[1].list = [_NULL_STRING, *excel_column_names]
        #     display_names = [row[0] for row in fm.values]
        #     autofill_column_names = {display_name: closest_col for display_name in display_names if (closest_col := get_closest_match_insensitive(display_name, excel_column_names, 0.75))}
        #     fm.values = [[name, autofill_column_names.get(name, _NULL_STRING)] for name in display_names]
        #### ---------------------------------------- ####

        if param_dict["gdb"].altered and not param_dict["output_path"].altered:
            timestamp_string: str = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
            out_name = f"MSAG_Analysis_{timestamp_string}.gdb"
            in_gdb = Path(unwrap(param_dict["gdb"]))
            out_gdb = in_gdb.parent / out_name
            param_dict["output_path"].value = str(out_gdb)

    def updateMessages(self, parameters):
        """Modify the messages created by internal validation for each tool
        parameter.  This method is called after internal validation."""
        param_dict = params_to_dict(parameters)

        # for fm, fm_cls in ((param_dict["msag_field_map"], MSAGFieldMap), (param_dict["tn_field_map"], TNFieldMap)):  # type: (arcpy.Parameter, type[MSAGComparatorFieldMap])  #### RE-ENABLE WHEN TN COMPARISON IS COMPLETE ####
        for fm, fm_cls in ((param_dict["msag_field_map"], MSAGFieldMap), ):  # type: (arcpy.Parameter, type[MSAGComparatorFieldMap])  #### REMOVE WHEN TN COMPARISON IS COMPLETE ####
            if not fm.enabled or not fm.values:
                fm.clearMessage()
                continue
            is_required: dict[str, bool] = {n: r for n, r in zip(fm_cls.get_display_names(), fm_cls.get_input_required())}
            missing_required: list[str] = []
            for display_name, excel_column in fm.values:  # type: (str, str)
                if is_required[display_name] and excel_column in {None, _NULL_STRING}:
                    missing_required.append(display_name)
            if missing_required:
                fm.setErrorMessage(f"The following required fields are missing: {', '.join(qquote(missing_required))}")
            else:
                fm.clearMessage()


    def execute(self, parameters: list[arcpy.Parameter], messages: GPMessenger):
        """The source code of the tool."""
        param_dict = params_to_dict(parameters)
        param_values = unwrap_to_dict(parameters)

        gdb = Path(param_values["gdb"])
        # create_relations = bool(param_values["create_relations"])

        msag_path = Path(v) if (v := param_values["msag"]) else None
        msag_fm: MSAGFieldMap | None
        if msag_path:
            msag_fm_values: dict[str, str | None] = {
                display_name: excel_column
                if excel_column != _NULL_STRING else None
                for display_name, excel_column
                in param_values["msag_field_map"]
            } if msag_path else None
            display_to_field: dict[str, str] = {f.metadata["display_name"]: f.name for f in attrs.fields(MSAGFieldMap)}
            msag_fm_values = {field_name: msag_fm_values[display_name] for display_name, field_name in display_to_field.items()}
            msag_fm = MSAGFieldMap(**msag_fm_values)
        else:
            msag_fm = None

        #### RE-ENABLE WHEN TN COMPARISON IS COMPLETE ####
        # tn_path = Path(v) if (v := param_values["tn"]) else None
        # tn_fm: TNFieldMap | None
        # if tn_path:
        #     tn_fm_values: dict[str, str | None] = {
        #         display_name: excel_column
        #         if excel_column != _NULL_STRING else None
        #         for display_name, excel_column
        #         in param_values["tn_field_map"]
        #     } if tn_path else None
        #     display_to_field: dict[str, str] = {f.metadata["display_name"]: f.name for f in attrs.fields(TNFieldMap)}
        #     tn_fm_values = {field_name: tn_fm_values[display_name] for display_name, field_name in display_to_field.items()}
        #     tn_fm = TNFieldMap(**tn_fm_values)
        # else:
        #     tn_fm = None
        #### ---------------------------------------- ####

        msag_issues: list[MSAGIssue] = []
        msag_issue_table_path: Path | None
        # tn_issues: list[MSAGIssue] = []  #### RE-ENABLE WHEN TN COMPARISON IS COMPLETE ####
        # tn_issue_table_path: Path | None  #### RE-ENABLE WHEN TN COMPARISON IS COMPLETE ####
        # aprx: ArcGISProject | None = get_project()
        # env_kwargs = {"addOutputsToMap": False} if create_relations and (aprx is not None) else {}
        # if create_relations and (aprx is None):
        #     messages.addWarningMessage("Could not get the 'CURRENT' ArcGISProject instance; 'create_relations' will be ignored.")

        with NG911MSAGComparator(
            workspace=gdb,
            respect_submit=True,
            use_edit_session=False,
            messenger=messages,
            msag_path=msag_path,
            msag_fields=msag_fm#,
            #### RE-ENABLE WHEN TN COMPARISON IS COMPLETE ####
            # tn_path=tn_path,
            # tn_fields=tn_fm
            #### ---------------------------------------- ####
            # **env_kwargs
        ) as comparator:
            if comparator.msag_path:
                # msag_issues += comparator.find_missing_msag_entries()
                msag_issues += comparator.find_missing_address_ranges()
                msag_issue_table_path = comparator.export_msag_issue_table()
            else:
                msag_issue_table_path = None
            #### RE-ENABLE WHEN TN COMPARISON IS COMPLETE ####
            # if comparator.tn_path:
            #     # tn_issues += comparator.find_missing_tn_entries()
            #     # tn_issues += comparator.find_missing_address_points()
            #     tn_issue_table_path = comparator.export_tn_issue_table()
            # else:
            #     tn_issue_table_path = None
            #### ---------------------------------------- ####

            param_dict["msag_issue_table"].value = str(msag_issue_table_path) if msag_issue_table_path else None
            # param_dict["tn_issue_table"].value = str(tn_issue_table_path) if tn_issue_table_path else None  #### RE-ENABLE WHEN TN COMPARISON IS COMPLETE ####

            # if create_relations and aprx:
            #     aprx: ArcGISProject | None = get_project()
            #     m: APRXMap = aprx.activeMap
            #     msag_issue_table_view: APRXTable | None = arcpy.management.MakeTableView(str(msag_issue_table_path), "MSAG Issues", )



if __name__ == "__main__":
    raise Exception("This module is a dependency of an ArcGIS Python Toolbox and should not be executed directly.")