import hashlib
import logging
import os
import platform
import re
import requests
import traceback
import secrets
import string
import subprocess

from app_config import DATABASES
from decouple import config as config_environment

from sqlalchemy import create_engine, pool
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from urllib.parse import quote_plus

from geopy.distance import geodesic
from geopy.geocoders import Nominatim

class HTTP404Error(Exception):
    def __init__(self, message="HTTP 404 - Not Found"):
        self.message = message
        super().__init__(self.message)


class HTTP500Error(Exception):
    def __init__(self, message="HTTP 500 - Internal Server Error"):
        self.message = message
        super().__init__(self.message)

class Logger():

    def __set_logger(self, client):
        log_directory = config_environment('LOG_DIRECTORY')
        log_filename = f'{client}.log'

        # Obtener el nombre del sistema operativo
        operating_system = platform.system()
        if operating_system == "Linux":
            # Comando para cambiar los permisos
            chmod_command = f"sudo chmod -R u+rwx {log_directory}"

            # Ejecutar el comando en la terminal
            subprocess.run(chmod_command, shell=True)

        logger = logging.getLogger(__name__)
        logger.setLevel(logging.DEBUG)

        log_path = os.path.join(log_directory, log_filename)
        file_handler = logging.FileHandler(log_path, encoding='utf-8')
        file_handler.setLevel(logging.DEBUG)

        formatter = logging.Formatter('%(asctime)s | %(levelname)s | %(message)s', "%Y-%m-%d %H:%M:%S")
        file_handler.setFormatter(formatter)

        if (logger.hasHandlers()):
            logger.handlers.clear()

        logger.addHandler(file_handler)

        return logger

    @classmethod
    def add_to_log(self, level, message, client='log'):
        try:
            logger = self.__set_logger(self, client)

            if (level == "critical"):
                logger.critical(message)
            elif (level == "debug"):
                logger.debug(message)
            elif (level == "error"):
                logger.error(message)
            elif (level == "info"):
                logger.info(message)
            elif (level == "warn"):
                logger.warn(message)
            elif (level == "success"):
                logger.info(message)
        except Exception as ex:
            print(traceback.format_exc())
            print(ex)

class Validate():

    @classmethod
    def validate_json_keys(self, json_data, array_no_required=[]):
        """
        Valida un JSON asegurándose de que todos los campos tengan un valor.

        Parameters:
        - json_data: El JSON a validar.
        - array_no_required: Arreglo de key que no son requeridos.

        Raises:
        - ValueError: Si se encuentra algún campo sin valor.

        Returns:
        - Tuple: Una tupla que contiene el mensaje de error y un indicador de éxito.
        """
        for key, value in json_data.items():
            if key not in array_no_required:
                if value is None or (isinstance(value, str) and value.strip() == ''):
                    return f'El campo "{key}" no tiene un valor válido.', True
        return None, False

class Stech():

    Base = declarative_base()

    @classmethod
    def get_session(self, connection_name):

        # Recuperar la conexión según el nombre dado
        data = DATABASES[connection_name]

        DB_HOST = data['DB_HOST']
        DB_USER = data['DB_USER']
        DB_PASSWORD = data['DB_PASSWORD']
        DB_PORT = data['DB_PORT']
        DB_NAME = data['DB_NAME']
        DB_TYPE = data['DB_TYPE']

        encoded_password = quote_plus(DB_PASSWORD)
        connection_string = f"{DB_TYPE}://{DB_USER}:{encoded_password}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
        engine = create_engine(connection_string, pool_pre_ping=True, poolclass=pool.NullPool)
        Session = sessionmaker(bind=engine)
        
        # Retorna la sesión
        return Session()


    @classmethod
    def object_to_json(self, instance):
        # Ignorar los atributos especiales de SQLAlchemy
        ignore_keys = dir(type(instance)) 

        data = {}
        for key, value in instance.__dict__.items():
            if key not in ignore_keys and not key.startswith('_'):
                data[key] = value

        return data
    

    @classmethod
    def make_password(self, password):
        
        # Generar una sal aleatoria
        characters = string.ascii_letters + string.digits + string.punctuation
        salt = ''.join(secrets.choice(characters) for i in range(12))  # Sal de 12 caracteres

        # Combinar la contraseña y la sal
        password_with_salt = password + salt

        # Aplicar el algoritmo de hash (en este caso, SHA-256)
        hashed_password = hashlib.sha256(password_with_salt.encode()).hexdigest()

        # Retornar la contraseña encriptada junto con la sal
        return f"{hashed_password}${salt}"
    
    
    @classmethod
    def validate_email_format(self, email):
        # Expresión regular para validar un formato básico de correo electrónico
        email_regex = r'^[\w\.-]+@[a-zA-Z\d\.-]+\.[a-zA-Z]{2,}$'

        # Verificar si el correo electrónico coincide con la expresión regular
        return bool(re.match(email_regex, email))
    
    
    @classmethod
    def getLatitudeLongitude(self, address):
        try:
            # Crear un objeto geolocator utilizando el proveedor Nominatim
            geolocator = Nominatim(user_agent="Nominatim")

            # Obtener la ubicación (latitud, longitud) a partir de la dirección
            location = geolocator.geocode(address)

            if location:
                latitude = location.latitude
                longitude = location.longitude
                return latitude, longitude
            else:
                return False, False
        except:
            print(address + " no se pudo encontrar")
            return False, False
        
    
    @classmethod
    def getLatitudeLongitudeLocal(self, destination, origin="json"):
        try:
            
            url = f"http://35.225.53.24/buscador/search?q={destination}&format={origin}&polygon_kml=1&addressdetails=1"

            payload = {}
            headers = {}

            response = requests.request("GET", url, headers=headers, data=payload)

            return response.json()[0]["lat"], response.json()[0]["lon"]

        except:
            print(origin + " no se pudo encontrar")
            return False, False