import requests
import traceback
from pathlib import Path
from requests.exceptions import ConnectionError as RequestsConnectionError, Timeout, RequestException
from urllib3.exceptions import NewConnectionError, MaxRetryError
from typing import Any

from shared.utils import Logger, TokenManager, ConfigManager

# =============== Validation Metadata Service ===============
class ValidationMetadataService:
    def __init__(self):
        # Logger
        self.logger = Logger.create_logger(logger_name = "Validation Metadata Service", log_file_name = "validator_app")

        # Helper Classes Instance
        self.configManagerInstance: ConfigManager = ConfigManager()
        self.tokenManagerInstance: TokenManager = TokenManager()
    
    # ========== Document Template ==========
    def get_document_template(self, production: bool, plan_type: str, validation_package_version: str) -> str:
        """Request the documenta template."""
        try:
            if production == True:
                providerURL: str = self.configManagerInstance.get_value(section = "PROVIDER", key = self.configManagerInstance.get_value(section = "SERVICES", key = "production"))
                mediaURL: str = self.configManagerInstance.get_value(section = "MEDIA", key = self.configManagerInstance.get_value(section = "SERVICES", key = "production"))
            else:
                providerURL: str = self.configManagerInstance.get_value(section = "PROVIDER", key = self.configManagerInstance.get_value(section = "SERVICES", key = "development"))
                mediaURL: str = self.configManagerInstance.get_value(section = "MEDIA", key = self.configManagerInstance.get_value(section = "SERVICES", key = "development"))
            
            templateVersionRequestUrl: str = providerURL + self.configManagerInstance.get_value(section = "METADATA", key = "version")
            
            tokens:dict[str, str] = self.tokenManagerInstance.read_tokens()
            
            templateVersionsResponse = self.request(url = templateVersionRequestUrl, tokens = tokens)
            templateVersionDicts: dict[str, str] = templateVersionsResponse.json()
            
            for template in templateVersionDicts:
                if template["versiune"] == validation_package_version:
                    if plan_type == "PUG":
                        documentTemplateURL: str = mediaURL + template["file_gpgk_pug"]
                    if plan_type == "PUD":
                        documentTemplateURL: str = mediaURL + template["file_gpgk_pud"]
                    if plan_type == "PUZ":
                        documentTemplateURL: str = mediaURL + template["file_gpgk_puz"]
                    if plan_type == "PATJ":
                        documentTemplateURL: str = mediaURL + template["file_gpgk_patj"]
                    if plan_type == "PMUD":
                        documentTemplateURL: str = mediaURL + template["file_gpgk_pmud"]
                        
            return documentTemplateURL
        except:
            traceBack = traceback.format_exc()
            self.logger.error(
                f"[{self.__class__.__name__} - {self.get_document_template.__name__}] "
                f"Failed to get the document template. \n{traceBack}")
    # =======================================
    
    # ========== Zip Structure Rules ==========
    def get_rules_zip_structure(self, production: bool, plan_type: str, validation_package_version: str) -> dict[str, str] | str:
        """Request the zip structure validation rules."""
        try:
            if production == True:
                providerURL: str = self.configManagerInstance.get_value(section = "PROVIDER", key = self.configManagerInstance.get_value(section = "SERVICES", key = "production"))
            else:
                providerURL: str = self.configManagerInstance.get_value(section = "PROVIDER", key = self.configManagerInstance.get_value(section = "SERVICES", key = "development"))
            
            tokens:dict[str, str] = self.tokenManagerInstance.read_tokens()
            
            rulesZipStructUrl: str = providerURL + self.configManagerInstance.get_value(section = "METADATA", key = "rules_zip_struct")
            zipStructUrl: str = rulesZipStructUrl.replace("cat_plan", plan_type).replace("ver", validation_package_version)
            
            zipStructRulesRequest = self.request(url = zipStructUrl, tokens = tokens)
            
            if type(zipStructRulesRequest) == str:
                return zipStructRulesRequest
            
            zipStructRulesSet = zipStructRulesRequest.json()
            return zipStructRulesSet
        except:
            traceBack = traceback.format_exc()
            self.logger.error(
                f"[{self.__class__.__name__} - {self.get_rules_zip_structure.__name__}] "
                f"Failed to get rules for zip structure validation. \n{traceBack}")
    # =========================================
    
    # ========== GPKG Structure Rules ==========
    def get_rules_gpkg_structure(self, production: bool, plan_type: str, validation_package_version: str) -> dict[str, str] | str:
        """Request the gpkg structure validation rules."""
        try:
            if production == True:
                providerURL: str = self.configManagerInstance.get_value(section = "PROVIDER", key = self.configManagerInstance.get_value(section = "SERVICES", key = "production"))
            else:
                providerURL: str = self.configManagerInstance.get_value(section = "PROVIDER", key = self.configManagerInstance.get_value(section = "SERVICES", key = "development"))
            
            tokens:dict[str, str] = self.tokenManagerInstance.read_tokens()
            
            rulesGpkgStructUrl: str = providerURL + self.configManagerInstance.get_value(section = "METADATA", key = "rules_gpkg_struct")
            gpkgStructUrl: str = rulesGpkgStructUrl.replace("cat_plan", plan_type).replace("ver", validation_package_version)
            
            gpkgStructRulesRequest = self.request(url = gpkgStructUrl, tokens = tokens)
            
            if type(gpkgStructRulesRequest) == str:
                return gpkgStructRulesRequest
            
            gpkgStructRulesSet = gpkgStructRulesRequest.json()
            return gpkgStructRulesSet
        except:
            traceBack = traceback.format_exc()
            self.logger.error(
                f"[{self.__class__.__name__} - {self.get_rules_gpkg_structure.__name__}] "
                f"Failed to get rules for gpkg structure validation. \n{traceBack}")
    # ==========================================
    
    # ========== GPKG Data Rules ==========
    def get_rules_gpkg_data(self, production: bool, plan_type: str, validation_package_version: str) -> dict[str, str] | str:
        """Request the gpkg data validation rules."""
        try:
            if production == True:
                providerURL: str = self.configManagerInstance.get_value(section = "PROVIDER", key = self.configManagerInstance.get_value(section = "SERVICES", key = "production"))
            else:
                providerURL: str = self.configManagerInstance.get_value(section = "PROVIDER", key = self.configManagerInstance.get_value(section = "SERVICES", key = "development"))
                
            tokens:dict[str, str] = self.tokenManagerInstance.read_tokens()
            
            rulesGpkgDataUrl: str = providerURL + self.configManagerInstance.get_value(section = "METADATA", key = "rules_gpkg_data")
            gpkgDataUrl: str = rulesGpkgDataUrl.replace("cat_plan", plan_type).replace("ver", validation_package_version)
            
            gpkgDataRulesRequest = self.request(url = gpkgDataUrl, tokens = tokens)
            
            if type(gpkgDataRulesRequest) == str:
                return gpkgDataRulesRequest
            
            gpkgDataRulesSet = gpkgDataRulesRequest.json()
            return gpkgDataRulesSet
        except:
            traceBack = traceback.format_exc()
            self.logger.error(
                f"[{self.__class__.__name__} - {self.get_rules_gpkg_data.__name__}] "
                f"Failed to get rules for gpkg data validation. \n{traceBack}")
    # =====================================
    
    # ========== Uat Limit ==========
    def get_uat_limit(self, production: bool, plan_type: str, validation_package_version: str, siruta: str = None) -> dict[str, str] | str:
        """Request the UAT limit for validation."""
        try:
            if production == True:
                providerURL: str = self.configManagerInstance.get_value(section = "PROVIDER", key = self.configManagerInstance.get_value(section = "SERVICES", key = "production"))
            else:
                providerURL: str = self.configManagerInstance.get_value(section = "PROVIDER", key = self.configManagerInstance.get_value(section = "SERVICES", key = "development"))
            
            tokens:dict[str, str] = self.tokenManagerInstance.read_tokens()
            
            uatValidationUrl: str = providerURL + self.configManagerInstance.get_value(section = "METADATA", key = "uat_validation")
            uatValidationUrl = uatValidationUrl.replace("cat_plan", plan_type).replace("ver", validation_package_version).replace("siruta", siruta)
            
            uatValidationResponse = self.request(url = uatValidationUrl, tokens = tokens)
            if type(uatValidationResponse) == str:
                return uatValidationResponse
            
            geoData = uatValidationResponse.json()
            return geoData
            
        except:
            traceBack = traceback.format_exc()
            self.logger.error(
                f"[{self.__class__.__name__} - {self.get_uat_limit.__name__}] "
                f"Failed to get the uat limit for validation. \n{traceBack}")
    # ===============================
    
    # ========== Request ==========
    def request(self, url: str, tokens: dict[str, str]) -> requests.Response | str:
        """Request data based on url and tokens."""
        try:
            response = requests.get(
                url = url,
                headers = {'Authorization': f'JWT {tokens["access"]}'},
                timeout = 60
            )
            
            return response
        
        except NewConnectionError as e:
            # This catches DNS failures, refused connections, etc.
            self.logger.error(f"[{self.__class__.__name__}.{self.request.__name__}] No internet connection or host unreachable: {e}")
            return "ConnectionError"
        
        except MaxRetryError as e:
            # This catches DNS failures, refused connections, etc.
            self.logger.error(f"[{self.__class__.__name__}.{self.request.__name__}] No internet connection or host unreachable: {e}")
            return "ConnectionError"
        
        except RequestsConnectionError as e:
            # This catches DNS failures, refused connections, etc.
            self.logger.error(f"[{self.__class__.__name__}.{self.request.__name__}] No internet connection or host unreachable: {e}")
            return "ConnectionError"
        
        except ConnectionError as e:
            # This catches DNS failures, refused connections, etc.
            self.logger.error(f"[{self.__class__.__name__}.{self.request.__name__}] No internet connection or host unreachable: {e}")
            return "ConnectionError"
            
        except Timeout as e:
            self.logger.error(f"[{self.__class__.__name__}.{self.request.__name__}] Request timed out: {e}")
            return "ConnectionError"
            
        except RequestException as e:
            # Catches all other requests-related errors
            self.logger.error(f"[{self.__class__.__name__}.{self.request.__name__}] Request failed: {e}")
            return "ConnectionError"
            
        except Exception as e:
            self.logger.error(f"[Class '{self.__class__.__name__}' - Function '{self.request.__name__}'] An error has occurred: {e}")
            return "Error"
    # =============================
# ===========================================================

if __name__ == "__main__":
    print(ValidationMetadataService().get_document_template(production=False, plan_type = "PUG", validation_package_version = "3.0.0"))