import os
import time
import traceback
import logging
import gc
from typing import Protocol
from osgeo import gdal

from .validation import ZipStructureRules, GpkgStructureRules, GpkgDataRules
from .writer import Writer
from .utils import RulesUtils

# ----------------------------
# Callback Function Signatures
# ----------------------------
class UatLimitGet(Protocol):
    def __call__(self, siruta: str) -> dict[str, str] | str: ...
class GetProgressbarUpdate(Protocol):
    def __call__(self) -> None: ...
# ----------------------------
# ----------------------------

# =============== Validation Rules ===============
class Rules:
    def __init__(self, logger: logging.Logger, loop_limit: float):
        # Logger
        self.logger: logging.Logger = logger
        # Loop limit
        self.loopLimit: float = loop_limit
        # Writer 
        self.rulesWriter = Writer(logger = self.logger)
        # Validation Rules Utils
        self.rulesUtils = RulesUtils(logger = self.logger, loop_limit = self.loopLimit)
        
        # Validation Rules
        self.zipStructureValidation = ZipStructureRules(logger = self.logger, loop_limit = self.loopLimit, rules_writer = self.rulesWriter)
        self.gpkgStructureRules = GpkgStructureRules(logger = self.logger, loop_limit = self.loopLimit, rules_writer = self.rulesWriter)
        self.gpkgDataRules = GpkgDataRules(logger = self.logger, loop_limit = self.loopLimit, rules_writer = self.rulesWriter)
        
    # ========== Validate Zip Structure ========== 
    def validate_zip_structure(self, rule_set: dict[str, str], file_path: str, plan_type: str, progressbar_update: GetProgressbarUpdate) -> bool:
        """Validate the structure of the ZIP archive.

        Args:
            rule_set (dict[str, str]): The validation rules metadata.
            file_path (str): The path of the ZIP archive.
            plan_type (str): The plan type.

        Returns:
            bool: True if the validation passes. False if the validation fails or an error occurs.
        """
        try:
            validationStatus: bool = True
            
            self.rulesUtils.read_zip_archive(zip_file_path = file_path, plan_type = plan_type)
            self.rulesUtils.extract_zip_archive(zip_file_path = file_path, plan_type = plan_type)
            
            for rule in rule_set:
                time.sleep(self.loopLimit)
                # Validate root dir number
                if rule["tip_regula_id"] == 3:
                    ruleStatus = self.zipStructureValidation.root_dir_number(
                        rule = rule,
                        zip_root_dir = self.rulesUtils.zipStructure["RootDirs"],
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                # Validate root dir name
                elif rule["tip_regula_id"] == 4:
                    ruleStatus = self.zipStructureValidation.root_dir_name(
                        rule = rule,
                        zip_root_dir = self.rulesUtils.zipStructure["RootDirs"],
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                # Validate the GPKG files number
                elif rule["tip_regula_id"] == 8:
                    ruleStatus = self.zipStructureValidation.gpkg_file_number(
                        rule = rule,
                        zip_gpkg_files = self.rulesUtils.zipStructure["GpkgFiles"],
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                # Validate the gpkg file name
                elif rule["tip_regula_id"] == 9:
                    ruleStatus = self.zipStructureValidation.gpkg_file_name(
                        rule = rule,
                        zip_gpkg_files = self.rulesUtils.zipStructure["GpkgFiles"],
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                # Validate ZIP archive tree structure
                elif rule["tip_regula_id"] == 50:
                    ruleStatus = self.zipStructureValidation.zip_tree_structure(
                        rule = rule,
                        zip_root_dir = self.rulesUtils.zipStructure["RootDirs"],
                        zip_folder_file_relation = self.rulesUtils.zipStructure["SubFolderFilesRelation"],
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                # Validate the number of pdf files inside avize
                elif rule["tip_regula_id"] == 12:
                    ruleStatus = self.zipStructureValidation.avize_files_number(
                        rule = rule,
                        avize_files = self.rulesUtils.zipStructure["AvizePdfFiles"],
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                # Validate the name of the pdf files inside avize
                elif rule["tip_regula_id"] == 13:
                    ruleStatus = self.zipStructureValidation.avize_files_name(
                        rule = rule,
                        avize_files = self.rulesUtils.zipStructure["AvizePdfFiles"],
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                # Validate if PDF file is valid
                elif rule["tip_regula_id"] == 14:
                    ruleStatus = self.zipStructureValidation.valid_pdf_files(
                        rule = rule,
                        extraction_folder = self.rulesUtils.zipData,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                # Validate if GPKG file is valid
                elif rule["tip_regula_id"] == 15:
                    ruleStatus = self.zipStructureValidation.valid_gpkg_files(
                        rule = rule,
                        extraction_folder = self.rulesUtils.zipData,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
            
            return validationStatus
        except:
            traceBack = traceback.format_exc()
            self.logger.error(
                f"[{self.__class__.__name__} - {self.validate_zip_structure.__name__}] "
                f"Failed to validate the zip structure. \n{traceBack}")
            return False
    # ============================================
    
    # ========== Validate GPKG Structure ==========
    def validate_gpkg_structure(self, rule_set: dict[str, str], file_path: str, document_template_url: str, plan_type: str, progressbar_update: GetProgressbarUpdate) -> bool:
        try:
            if file_path.endswith("zip"):
                # Get the path/url for gdal dataset 
                gpkgList: list[str] = []
                for rootFolder, _, files in os.walk(self.rulesUtils.zipData):
                        time.sleep(self.loopLimit)
                        for file in files:
                            if file.endswith((".gpkg", ".GPKG")):
                                gpkgList.append(f"{rootFolder}\\{file}")
                documentationGpkgPath: str = gpkgList[0]
            
            elif file_path.endswith("gpkg"):
                documentationGpkgPath: str = file_path
            # Paths / Url
            templateGpkgUrl: str = document_template_url
            # Load gdal datasets
            self.rulesUtils.load_gpkg(gpkg_type = "Documentation", gpkg_file_path = documentationGpkgPath)
            self.rulesUtils.load_gpkg(gpkg_type = "Template", gpkg_template_url = templateGpkgUrl)
            # Read gdal datasets data
            self.rulesUtils.read_gpkg_data(gpkg_dataset = self.rulesUtils.gpkgDocumentation, gpkg_type = "Documentation", plan_type = plan_type)
            self.rulesUtils.read_gpkg_data(gpkg_dataset = self.rulesUtils.gpkgTemplate, gpkg_type = "Template", plan_type = plan_type)
            
            # Verifies the gpkg coord system
            ruleStatus = self.gpkgStructureRules.layer_coord_system(
                rule = rule_set,
                documentation_gpkg = self.rulesUtils.gpkgDocumentation,
                template_gpkg = self.rulesUtils.gpkgTemplate,
                plan_type = plan_type
            )


            # Validate Layer Number
            ruleStatus = self.gpkgStructureRules.layer_number(
                rule = rule_set,
                documentation_layers_number = self.rulesUtils.gpkgDocumentationDataStructure["LayerNumber"],
                template_layers_number = self.rulesUtils.gpkgTemplateDataStructure["LayerNumber"],
                plan_type = plan_type
            )
            
            if ruleStatus == False:
                progressbar_update()
                return False
            
            # Validate Layer Names
            ruleStatus = self.gpkgStructureRules.layer_names(
                rule = rule_set,
                documentation_layer_names = self.rulesUtils.gpkgDocumentationDataStructure["LayerNames"],
                template_layers_names = self.rulesUtils.gpkgTemplateDataStructure["LayerNames"],
                plan_type = plan_type
            )
            
            if ruleStatus == False:
                progressbar_update()
                return False
            
            # Validate the Layer Geom Type
            ruleStatus = self.gpkgStructureRules.layer_geoms_type(
                rule = rule_set,
                documentation_layer_geoms = self.rulesUtils.gpkgDocumentationDataStructure["LayerGeoms"],
                template_layer_geoms = self.rulesUtils.gpkgTemplateDataStructure["LayerGeoms"],
                plan_type = plan_type
            )
            
            if ruleStatus == False:
                progressbar_update()
                return False
            
            # Validate column number
            ruleStatus = self.gpkgStructureRules.layer_column_number(
                rule = rule_set,
                documentation_layer_column_numbers = self.rulesUtils.gpkgDocumentationDataStructure["LayerColumnsNumber"],
                template_layer_column_numbers = self.rulesUtils.gpkgTemplateDataStructure["LayerColumnsNumber"],
                plan_type = plan_type
            )
            
            if ruleStatus == False:
                progressbar_update()
                return False
            
            # Validate column names
            ruleStatus = self.gpkgStructureRules.layer_column_name(
                rule = rule_set,
                documentation_layer_column_names = self.rulesUtils.gpkgDocumentationDataStructure["LayerColumnsNames"],
                template_layer_column_names = self.rulesUtils.gpkgTemplateDataStructure["LayerColumnsNames"],
                plan_type = plan_type
            )
            
            if ruleStatus == False:
                progressbar_update()
                return False
            
            ruleStatus = self.gpkgStructureRules.layer_column_data_type(
                rule = rule_set,
                documentation_layer_column_data_type = self.rulesUtils.gpkgDocumentationDataStructure["LayerColumnsDataTypes"],
                template_layer_column_data_type = self.rulesUtils.gpkgTemplateDataStructure["LayerColumnsDataTypes"],
                plan_type = plan_type
            )
            
            if ruleStatus == False:
                progressbar_update()
                return False
            
            progressbar_update()
            self.rulesWriter.validation_pass(rule = rule_set)
            
            
            
        except:
            traceBack = traceback.format_exc()
            self.logger.error(
                f"[{self.__class__.__name__} - {self.validate_gpkg_structure.__name__}] "
                f"Failed to validate the gpkg structure. \n{traceBack}")
    # =============================================
    
    # ========== Validate GPKG Data ==========
    def validate_gpkg_data(self, rule_set: dict[str, str], uat_limit_get: UatLimitGet, plan_type: str, progressbar_update: GetProgressbarUpdate) -> bool:
        """Validates the data of the documentation gpkg.

        Args:
            rule_set (dict[str, str]): The validation rules metadata.
            uat_limit_get (UatLimitGet): The function to get the UAT limit.
            plan_type (str): The plan type.

        Returns:
            bool: True if the rules pass. False if the rules fail or an error occurs.
        """
        try:
            # Validation status
            validationStatus: bool = True
            
            # GPKG Gdal datasets
            gpkgDocumentation: gdal.Dataset = self.rulesUtils.gpkgDocumentation
            gpkgTemplate: gdal.Dataset = self.rulesUtils.gpkgTemplate
            
            # Extracting template GPKG metadata
            self.rulesUtils.extract_template_metadata(
                template_gpkg = gpkgTemplate,
                plan_type = plan_type
            )
            templateMetadata = self.rulesUtils.gpkgTemplateMetadata

            for rule in rule_set:
                if rule["tip_regula_id"] == 20:
                    ruleStatus = self.gpkgDataRules.layers_contain_data(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()

                elif rule["tip_regula_id"] == 21:
                    ruleStatus = self.gpkgDataRules.columns_contain_data(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                    
                elif rule["tip_regula_id"] == 23:
                    ruleStatus = self.gpkgDataRules.columns_data_validation(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        template_metadata = templateMetadata,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 24:
                    ruleStatus = self.gpkgDataRules.temporal_data_validation(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        template_metadata = templateMetadata,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 25:
                    ruleStatus = self.gpkgDataRules.hilucs_data_correlation(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        template_metadata = templateMetadata,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 26:
                    ruleStatus = self.gpkgDataRules.zonal_code_correlation(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 27:
                    ruleStatus = self.gpkgDataRules.coordonate_system(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 29:
                    ruleStatus = self.gpkgDataRules.geoms_values(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 31:
                    ruleStatus = self.gpkgDataRules.geoms_valid(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 32:
                    ruleStatus = self.gpkgDataRules.administrative_limit(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        uat_limit = uat_limit_get,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 33:
                    ruleStatus = self.gpkgDataRules.geoms_in_plan(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 34:
                    ruleStatus = self.gpkgDataRules.geoms_cover_plan(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 35:
                    ruleStatus = self.gpkgDataRules.geoms_overlap(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 36:
                    ruleStatus = self.gpkgDataRules.geoms_sliver_polygons(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 37:
                    ruleStatus = self.gpkgDataRules.unique_codes(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 38:
                    ruleStatus = self.gpkgDataRules.cod_type_equal(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 39:
                    ruleStatus = self.gpkgDataRules.cod_type_correlation(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        template_metadata = templateMetadata,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 40:
                    ruleStatus = self.gpkgDataRules.temporal_dates_equal(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 41:
                    ruleStatus = self.gpkgDataRules.coordonates_types(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 42:
                    ruleStatus = self.gpkgDataRules.geom_area(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 43:
                    ruleStatus = self.gpkgDataRules.geom_length(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 44:
                    ruleStatus = self.gpkgDataRules.area_equal_to_planspatial(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 46:
                    ruleStatus = self.gpkgDataRules.regulament_zones_codes_types(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 47:
                    ruleStatus = self.gpkgDataRules.temporal_data_matches(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 48:
                    ruleStatus = self.gpkgDataRules.zones_temporal_dates(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 52:
                    ruleStatus = self.gpkgDataRules.regulament_zones_unique_codes(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 53:
                    ruleStatus = self.gpkgDataRules.regulament_detaliat_codes_types(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 54:
                    ruleStatus = self.gpkgDataRules.zones_code_type_consistency(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 55:
                    ruleStatus = self.gpkgDataRules.intravilan_spatial_combinations(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type,
                        siruta_parents = templateMetadata["Siruta"]
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
                
                elif rule["tip_regula_id"] == 56:
                    ruleStatus = self.gpkgDataRules.columns_regex_validation(
                        rule = rule,
                        documentation_gpkg = gpkgDocumentation,
                        plan_type = plan_type
                    )
                    if ruleStatus == False:
                        validationStatus = False
                    progressbar_update()
            
            return validationStatus
        except:
            traceBack = traceback.format_exc()
            self.logger.error(
                f"[{self.__class__.__name__} - {self.validate_gpkg_data.__name__}] "
                f"Failed to validate the gpkg data. \n{traceBack}")
    # ========================================
    
    # ========== Clear Memory ==========
    def clear_memory(self):
        try:
            # Collect garbage
            gc.collect()
        except:
            traceBack = traceback.format_exc()
            self.logger.error(
                f"[{self.__class__.__name__} - {self.clear_memory.__name__}] "
                f"Failed to read the gpkg data. \n{traceBack}")
    # ==================================
# ================================================