from requests.exceptions import RequestsDependencyWarning
import warnings
import json
import requests
from OpenSSL.crypto import (
    dump_certificate_request,
    dump_privatekey,
    PKey,
    TYPE_RSA,
    X509Req
)
from OpenSSL.SSL import FILETYPE_PEM
import os
import logging
import shutil
import subprocess
from requests.auth import HTTPBasicAuth
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


warnings.filterwarnings("ignore", category=RequestsDependencyWarning)

# Configuración básica del logger

log_path = 'logs/sdk_logs.log'

log_dir = os.path.dirname(log_path)

if not os.path.exists(log_dir):
    os.makedirs(log_dir)

logging.basicConfig(
    level=logging.NOTSET,  # Nivel mínimo de severidad a registrar
    # Formato del mensaje de log
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler(log_path),  # Registra en un archivo
        logging.StreamHandler()  # También muestra en la consola
    ]
)


class CAPIFProviderConnector:
    """
    Τhis class is responsible for onboarding an exposer (eg. NEF emulator) to CAPIF
    """

    def __init__(self, config_file: str):
        """
        Inicializa el conector CAPIFProvider con los parámetros especificados en el archivo de configuración.
        """
        # Cargar configuración desde archivo si es necesario
        config_file = os.path.abspath(config_file)
        self.config_path = os.path.dirname(config_file)+"/"
        config = self.__load_config_file(config_file)
        debug_mode = os.getenv('DEBUG_MODE', config.get(
            'debug_mode', 'False')).strip().lower()
        if debug_mode == "false":
            debug_mode = False
        # Inicializar logger
        self.logger = logging.getLogger(self.__class__.__name__)
        if debug_mode:
            self.logger.setLevel(logging.DEBUG)
        else:
            self.logger.setLevel(logging.WARNING)

        urllib_logger = logging.getLogger("urllib3")
        if not debug_mode:
            urllib_logger.setLevel(logging.WARNING)
        else:
            urllib_logger.setLevel(logging.DEBUG)

        try:

            provider_general_folder = os.path.abspath(
                os.getenv('PROVIDER_FOLDER', config.get('provider_folder', '')).strip())
            capif_host = os.getenv(
                'CAPIF_HOST', config.get('capif_host', '')).strip()
            capif_register_host = os.getenv(
                'REGISTER_HOST', config.get('register_host', '')).strip()
            capif_https_port = str(
                os.getenv('CAPIF_HTTPS_PORT', config.get('capif_https_port', '')).strip())
            capif_register_port = str(os.getenv(
                'CAPIF_REGISTER_PORT', config.get('capif_register_port', '')).strip())
            capif_provider_username = os.getenv(
                'CAPIF_USERNAME', config.get('capif_username', '')).strip()
            capif_provider_password = os.getenv(
                'CAPIF_PASSWORD', config.get('capif_password', '')).strip()

            csr_common_name = os.getenv(
                'CSR_COMMON_NAME', config.get('csr_common_name', '')).strip()
            csr_organizational_unit = os.getenv(
                'CSR_ORGANIZATIONAL_UNIT', config.get('csr_organizational_unit', '')).strip()
            csr_organization = os.getenv(
                'CSR_ORGANIZATION', config.get('csr_organization', '')).strip()
            crs_locality = os.getenv(
                'CRS_LOCALITY', config.get('crs_locality', '')).strip()
            csr_state_or_province_name = os.getenv('CSR_STATE_OR_PROVINCE_NAME', config.get(
                'csr_state_or_province_name', '')).strip()
            csr_country_name = os.getenv(
                'CSR_COUNTRY_NAME', config.get('csr_country_name', '')).strip()
            csr_email_address = os.getenv(
                'CSR_EMAIL_ADDRESS', config.get('csr_email_address', '')).strip()
            apfs = os.getenv('APFS', config.get('apfs', '')).strip()
            aefs = os.getenv('AEFS', config.get('aefs', '')).strip()
            api_description_path = os.path.abspath(os.getenv(
                'API_DESCRIPTION_PATH', config.get('api_description_path', '')).strip())

            if not capif_host:
                self.logger.warning(
                    "CAPIF_HOST is not provided; defaulting to an empty string")
            if not capif_provider_username:
                self.logger.error(
                    "CAPIF_PROVIDER_USERNAME is required but not provided")
                raise ValueError("CAPIF_PROVIDER_USERNAME is required")

            self.provider_folder = os.path.join(
                provider_general_folder, capif_provider_username)
            os.makedirs(self.provider_folder, exist_ok=True)

            self.capif_host = capif_host.strip()
            self.capif_provider_username = capif_provider_username
            self.capif_provider_password = capif_provider_password
            self.capif_register_host = capif_register_host
            self.capif_register_port = capif_register_port
            self.csr_common_name = csr_common_name
            self.csr_organizational_unit = csr_organizational_unit
            self.csr_organization = csr_organization
            self.crs_locality = crs_locality
            self.csr_state_or_province_name = csr_state_or_province_name
            self.csr_country_name = csr_country_name
            self.csr_email_address = csr_email_address
            self.aefs = int(aefs)
            self.apfs = int(apfs)
            config = config["publish_req"]
            self.publish_req = {
                "service_api_id": os.getenv('PUBLISH_REQ_SERVICE_API_ID', config.get('service_api_id', '')).strip(),
                "publisher_apf_id": os.getenv('PUBLISH_REQ_PUBLISHER_APF_ID', config.get('publisher_apf_id', '')).strip(),
                "publisher_aefs_ids": os.getenv('PUBLISH_REQ_PUBLISHER_AEFS_IDS', config.get('publisher_aefs_ids', '')).split(',')
            }

            self.api_description_path = api_description_path

            self.capif_https_port = str(capif_https_port)

            if len(self.capif_https_port) == 0 or int(self.capif_https_port) == 443:
                self.capif_https_url = f"https://{capif_host.strip()}/"
            else:
                self.capif_https_url = f"https://{capif_host.strip()}:{self.capif_https_port.strip()}/"

            if len(capif_register_port) == 0:
                self.capif_register_url = f"https://{capif_register_host.strip()}:8084/"
            else:
                self.capif_register_url = f"https://{capif_register_host.strip()}:{capif_register_port.strip()}/"

            self.logger.info(
                "CAPIFProviderConnector initialized with the capif-sdk-config.json parameters")

        except Exception as e:
            self.logger.error(f"Error during initialization: {e}")
            raise

    def __store_certificate(self) -> None:
        # Retrieves and stores the cert_server.pem from CAPIF.
        self.logger.info(
            "Retrieving capif_cert_server.pem, this may take a few minutes.")

        cmd = f"openssl s_client -connect {self.capif_host}:{self.capif_https_port} | openssl x509 -text > {self.provider_folder}/capif_cert_server.pem"

        try:
            # Redirige la salida estándar y de errores a os.devnull para ocultar los logs
            with open(os.devnull, 'w') as devnull:
                subprocess.run(cmd, shell=True, check=True,
                               stdout=devnull, stderr=devnull)

            cert_file = os.path.join(
                self.provider_folder, "capif_cert_server.pem")
            if os.path.exists(cert_file) and os.path.getsize(cert_file) > 0:
                self.logger.info("cert_server.pem successfully generated!")
            else:
                self.logger.error("Failed to generate cert_server.pem.")
                raise FileNotFoundError(
                    f"Certificate file not found at {cert_file}")
        except subprocess.CalledProcessError as e:
            self.logger.error(f"Command failed: {e}")
            raise
        except Exception as e:
            self.logger.error(f"Error occurred: {e}")
            raise

    def __load_config_file(self, config_file: str):
        """Carga el archivo de configuración."""
        try:
            with open(config_file, 'r') as file:
                return json.load(file)
        except FileNotFoundError:
            self.logger.warning(
                f"Configuration file {config_file} not found. Using defaults or environment variables.")
            return {}

    def __create_private_and_public_keys(self, api_prov_func_role) -> bytes:
        """
        Creates private and public keys in the certificates folder.
        :return: The contents of the public key
        """
        private_key_path = os.path.join(
            self.provider_folder, f"{api_prov_func_role}_private_key.key")
        csr_file_path = os.path.join(
            self.provider_folder, f"{api_prov_func_role}_public.csr")

        # Create key pair
        key = PKey()
        key.generate_key(TYPE_RSA, 2048)

        # Create CSR
        req = X509Req()
        subject = req.get_subject()
        subject.CN = api_prov_func_role.lower()
        subject.O = self.csr_organization
        subject.OU = self.csr_organizational_unit
        subject.L = self.crs_locality
        subject.ST = self.csr_state_or_province_name
        subject.C = self.csr_country_name
        subject.emailAddress = self.csr_email_address

        req.set_pubkey(key)
        req.sign(key, "sha256")

        # Write CSR and private key to files
        with open(csr_file_path, "wb") as csr_file:
            public_key = dump_certificate_request(FILETYPE_PEM, req)
            csr_file.write(public_key)

        with open(private_key_path, "wb") as private_key_file:
            private_key_file.write(dump_privatekey(FILETYPE_PEM, key))

        return public_key

    def __onboard_exposer_to_capif(self, access_token, capif_onboarding_url):
        self.logger.info(
            "Onboarding Provider to CAPIF and waiting signed certificate by giving our public keys to CAPIF")

        url = f"{self.capif_https_url}{capif_onboarding_url}"
        headers = {
            "Authorization": f"Bearer {access_token}",
            "Content-Type": "application/json",
        }

        # Crear la lista de roles sin indexar
        roles = ["AMF"]
        for n in range(1, self.aefs + 1):
            roles.append("AEF")

        for n in range(1, self.apfs + 1):
            roles.append("APF")

        # Construir el payload con los roles sin indexar
        payload = {
            "apiProvFuncs": [
                {"regInfo": {"apiProvPubKey": ""}, "apiProvFuncRole": role,
                    "apiProvFuncInfo": f"{role.lower()}"}
                for role in roles
            ],
            "apiProvDomInfo": "This is provider",
            "suppFeat": "fff",
            "failReason": "string",
            "regSec": access_token,
        }

        # Generar los roles indexados para la creación de certificados
        indexedroles = ["AMF"]
        for n in range(1, self.aefs + 1):
            indexedroles.append(f"AEF-{n}")

        for n in range(1, self.apfs + 1):
            indexedroles.append(f"APF-{n}")

        # Guardar las claves públicas y generar los certificados con roles indexados
        for i, api_func in enumerate(payload["apiProvFuncs"]):
            # Generar las claves públicas con el rol indexado, pero no actualizar el payload con el rol indexado
            public_key = self.__create_private_and_public_keys(indexedroles[i])

            # Asignar la clave pública al payload
            api_func["regInfo"]["apiProvPubKey"] = public_key.decode("utf-8")

        try:
            response = requests.post(
                url,
                headers=headers,
                data=json.dumps(payload),
                verify=os.path.join(self.provider_folder, "ca.crt"),
            )
            response.raise_for_status()
            self.logger.info(
                "Provider onboarded and signed certificate obtained successfully")
            return response.json()
        except requests.exceptions.RequestException as e:
            self.logger.error(
                f"Onboarding failed: {e} - Response: {response.text}")
            raise

    def __write_to_file(self, onboarding_response, capif_registration_id, publish_url):
        self.logger.info("Saving the most relevant onboarding data")

        # Generar los roles indexados para la correspondencia
        indexedroles = ["AMF"]
        for n in range(1, self.aefs + 1):
            indexedroles.append(f"AEF-{n}")

        for n in range(1, self.apfs + 1):
            indexedroles.append(f"APF-{n}")

        # Guardar los certificados con los nombres indexados
        for i, func_profile in enumerate(onboarding_response["apiProvFuncs"]):
            role = indexedroles[i].lower()
            cert_path = os.path.join(self.provider_folder, f"{role}.crt")
            with open(cert_path, "wb") as cert_file:
                cert_file.write(
                    func_profile["regInfo"]["apiProvCert"].encode("utf-8"))

        # Guardar los detalles del proveedor
        provider_details_path = os.path.join(
            self.provider_folder, "capif_provider_details.json")
        with open(provider_details_path, "w") as outfile:
            data = {
                "capif_registration_id": capif_registration_id,
                "publish_url": publish_url,
                **{f"{indexedroles[i]}_api_prov_func_id": api_prov_func["apiProvFuncId"]
                   for i, api_prov_func in enumerate(onboarding_response["apiProvFuncs"])}
            }
            json.dump(data, outfile, indent=4)

        self.logger.info("Data saved")

    def __save_capif_ca_root_file_and_get_auth_token(self):
        url = f"{self.capif_register_url}getauth"
        self.logger.info(
            "Saving CAPIF CA root file and getting auth token with user and password given by the CAPIF administrator")

        try:
            response = requests.get(
                url,
                headers={"Content-Type": "application/json"},
                auth=HTTPBasicAuth(self.capif_provider_username,
                                   self.capif_provider_password),
                verify=False
            )
            response.raise_for_status()

            self.logger.info("Authorization acquired successfully")

            response_payload = response.json()
            ca_root_file_path = os.path.join(self.provider_folder, "ca.crt")

            with open(ca_root_file_path, "wb") as ca_root_file:
                ca_root_file.write(response_payload["ca_root"].encode("utf-8"))

            self.logger.info(
                "CAPIF CA root file saved and auth token obtained successfully")
            return response_payload

        except requests.exceptions.RequestException as e:
            self.logger.error(
                f"Error acquiring authorization: {e} - Response: {response.text}")
            raise

    def onboard_provider(self) -> None:
        """
        Retrieves and stores the certificate from CAPIF, acquires authorization, and registers the provider.
        """
        # Store the certificate
        self.__store_certificate()

        # Retrieve CA root file and get authorization token
        capif_postauth_info = self.__save_capif_ca_root_file_and_get_auth_token()

        # Extract necessary information
        capif_onboarding_url = capif_postauth_info["ccf_api_onboarding_url"]
        access_token = capif_postauth_info["access_token"]
        ccf_publish_url = capif_postauth_info["ccf_publish_url"]

        # Onboard provider to CAPIF
        onboarding_response = self.__onboard_exposer_to_capif(
            access_token, capif_onboarding_url
        )

        # Save onboarding details to file
        capif_registration_id = onboarding_response["apiProvDomId"]
        self.__write_to_file(
            onboarding_response, capif_registration_id, ccf_publish_url
        )

    def publish_services(self) -> dict:
        """
        Publishes services to CAPIF and returns the published services dictionary.

        :param service_api_description_json_full_path: The full path of the service_api_description.json containing
        the endpoints to be published.
        :return: The published services dictionary that was saved in CAPIF.
        """
        self.logger.info("Starting the service publication process")

        # Load provider details
        provider_details_path = os.path.join(
            self.provider_folder, "capif_provider_details.json")
        self.logger.info(
            f"Loading provider details from {provider_details_path}")

        provider_details = self.__load_provider_api_details()

        publish_url = provider_details["publish_url"]

        chosenAPFsandAEFs = self.publish_req

        APF_api_prov_func_id = chosenAPFsandAEFs["publisher_apf_id"]
        AEFs_list = chosenAPFsandAEFs["publisher_aefs_ids"]

        apf_number = None
        for key, value in provider_details.items():
            if value == APF_api_prov_func_id and key.startswith("APF-"):
                apf_inter = key.split("-")[1]
                # Obtener el número del APF
                apf_number = apf_inter.split("_")[0]
                break

        if apf_number is None:
            self.logger.error(
                f"No matching APF found for publisher_apf_id: {APF_api_prov_func_id}")
            raise ValueError("Invalid publisher_apf_id")
        service_api_description_json_full_path = self.api_description_path
        # Leer y modificar la descripción de la API de servicio
        self.logger.info(
            f"Reading and modifying service API description from {service_api_description_json_full_path}")

        try:
            with open(service_api_description_json_full_path, "r") as service_file:
                data = json.load(service_file)

                # Verificamos que el número de AEFs coincide con el número de perfiles
                if len(AEFs_list) != len(data.get("aefProfiles", [])):
                    self.logger.error(
                        "The number of AEFs in publisher_aefs_ids does not match the number of profiles in aefProfiles")
                    raise ValueError(
                        "Mismatch between number of AEFs and profiles")

                # Asignamos los AEFs correspondientes
                for profile, aef_id in zip(data.get("aefProfiles", []), AEFs_list):
                    profile["aefId"] = aef_id

                self.logger.info(
                    "Service API description modified successfully")

                # Guardamos los cambios en el archivo
                with open(service_api_description_json_full_path, "w") as service_file:
                    json.dump(data, service_file, indent=4)

        except FileNotFoundError:
            self.logger.error(
                f"Service API description file not found: {service_api_description_json_full_path}")
            raise
        except json.JSONDecodeError as e:
            self.logger.error(
                f"Error decoding JSON from file {service_api_description_json_full_path}: {e}")
            raise
        except ValueError as e:
            self.logger.error(f"Error with the input data: {e}")
            raise

        # Publish services
        url = f"{self.capif_https_url}{publish_url.replace('<apfId>', APF_api_prov_func_id)}"
        cert = (
            os.path.join(self.provider_folder, f"apf-{apf_number}.crt"),
            os.path.join(self.provider_folder,
                         f"apf-{apf_number}_private_key.key"),
        )

        self.logger.info(f"Publishing services to URL: {url}")

        try:
            response = requests.post(
                url,
                headers={"Content-Type": "application/json"},
                data=json.dumps(data),
                cert=cert,
                verify=os.path.join(self.provider_folder, "ca.crt"),
            )
            response.raise_for_status()
            self.logger.info("Services published successfully")

            # Save response to file
            capif_response_text = response.text

            capif_response_json = json.loads(capif_response_text)

            # Default name if apiName is missing
            file_name = capif_response_json.get("apiName", "default_name")
            id = capif_response_json.get("apiId", "default_id")
            output_path = os.path.join(
                self.provider_folder, f"CAPIF-{file_name}-{id}-api.json")

            with open(output_path, "w") as outfile:
                outfile.write(capif_response_text)
            self.logger.info(f"CAPIF response saved to {output_path}")
            output_path = os.path.join(
                self.provider_folder, "Published-Apis.json")

            # Leer el archivo existente de APIs publicados
            published_apis = {}
            if os.path.exists(output_path):
                with open(output_path, "r") as outfile:
                    published_apis = json.load(outfile)

            # Agregar el nuevo API publicado
            published_apis[file_name] = id

            # Escribir el archivo actualizado de APIs publicados
            with open(output_path, "w") as outfile:
                json.dump(published_apis, outfile, indent=4)
            self.logger.info(
                f"API '{file_name}' with ID '{id}' added to Published Apis.")
            return json.loads(capif_response_text)

        except requests.RequestException as e:
            self.logger.error(
                f"Request to CAPIF failed: {e} - Response: {response.text}")
            raise
        except Exception as e:
            self.logger.error(
                f"Unexpected error during service publication: {e} - Response: {response.text}")
            raise

    def unpublish_service(self) -> dict:
        """
        Publishes services to CAPIF and returns the published services dictionary.

        :param service_api_description_json_full_path: The full path of the service_api_description.json containing
        the endpoints to be published.
        :return: The published services dictionary that was saved in CAPIF.
        """
        self.logger.info("Starting the service unpublication process")
        provider_details_path = os.path.join(
            self.provider_folder, "capif_provider_details.json")
        self.logger.info(
            f"Loading provider details from {provider_details_path}")

        provider_details = self.__load_provider_api_details()
        publish_url = provider_details["publish_url"]

        # Load provider details
        publish = self.publish_req
        api_id = "/" + publish["service_api_id"]
        APF_api_prov_func_id = publish["publisher_apf_id"]
        AEFs_list = publish["publisher_aefs_ids"]
        apf_number = None
        for key, value in provider_details.items():
            if value == APF_api_prov_func_id and key.startswith("APF-"):
                apf_inter = key.split("-")[1]
                # Obtener el número del APF
                apf_number = apf_inter.split("_")[0]
                break

        if apf_number is None:
            self.logger.error(
                f"No matching APF found for publisher_apf_id: {APF_api_prov_func_id}")
            raise ValueError("Invalid publisher_apf_id")

        self.logger.info(
            f"Loading provider details from {provider_details_path}")

        url = f"{self.capif_https_url}{publish_url.replace('<apfId>', APF_api_prov_func_id)}{api_id}"

        cert = (
            os.path.join(self.provider_folder, f"apf-{apf_number}.crt"),
            os.path.join(self.provider_folder,
                         f"apf-{apf_number}_private_key.key"),
        )

        self.logger.info(f"Unpublishing service to URL: {url}")

        try:
            response = requests.delete(
                url,
                headers={"Content-Type": "application/json"},
                cert=cert,
                verify=os.path.join(self.provider_folder, "ca.crt"),
            )

            response.raise_for_status()

            directory = self.provider_folder

            # Iterar sobre todos los archivos en el directorio
            for filename in os.listdir(directory):
                path = os.path.join(directory, filename)

                # Verificar si el archivo empieza con 'CAPIF-'

                if filename.startswith("CAPIF-") and publish["service_api_id"] in filename:

                    # Salir del bucle si el archivo es eliminado
                    os.remove(path)
                    break

            output_path = os.path.join(
                self.provider_folder, "Published-Apis.json")

            # Leer el archivo existente de APIs publicados
            published_apis = {}
            if os.path.exists(output_path):
                with open(output_path, "r") as outfile:
                    published_apis = json.load(outfile)

            # ID del API que deseas eliminar
            # Reemplaza con el ID específico
            api_id_to_delete = publish["service_api_id"]

            # Buscar y eliminar el API por su ID
            api_name_to_delete = None
            for name, id in published_apis.items():
                if id == api_id_to_delete:
                    api_name_to_delete = name
                    break

            if api_name_to_delete:
                del published_apis[api_name_to_delete]
                self.logger.info(
                    f"API with ID '{api_id_to_delete}' removed from Published Apis.")
            else:
                self.logger.warning(
                    f"API with ID '{api_id_to_delete}' not found in Published Apis.")

            # Escribir el archivo actualizado de APIs publicados
            with open(output_path, "w") as outfile:
                json.dump(published_apis, outfile, indent=4)

            self.logger.info("Services unpublished successfully")

        except requests.RequestException as e:
            self.logger.error(
                f"Request to CAPIF failed: {e} - Response: {response.text}")
            raise
        except Exception as e:
            self.logger.error(
                f"Unexpected error during service unpublication: {e} - Response: {response.text}")
            raise

    def get_service(self) -> dict:
        """
        Publishes services to CAPIF and returns the published services dictionary.

        :param service_api_description_json_full_path: The full path of the service_api_description.json containing
        the endpoints to be published.
        :return: The published services dictionary that was saved in CAPIF.
        """
        self.logger.info("Starting the service unpublication process")

        provider_details_path = os.path.join(
            self.provider_folder, "capif_provider_details.json")
        self.logger.info(
            f"Loading provider details from {provider_details_path}")

        provider_details = self.__load_provider_api_details()
        publish_url = provider_details["publish_url"]

        chosenAPFsandAEFs = self.publish_req

        APF_api_prov_func_id = chosenAPFsandAEFs["publisher_apf_id"]

        api_id = "/" + chosenAPFsandAEFs["service_api_id"]

        apf_number = None
        for key, value in provider_details.items():
            if value == APF_api_prov_func_id and key.startswith("APF-"):
                apf_inter = key.split("-")[1]
                # Obtener el número del APF
                apf_number = apf_inter.split("_")[0]
                break

        if apf_number is None:
            self.logger.error(
                f"No matching APF found for publisher_apf_id: {APF_api_prov_func_id}")
            raise ValueError("Invalid publisher_apf_id")

        url = f"{self.capif_https_url}{publish_url.replace('<apfId>', APF_api_prov_func_id)}{api_id}"

        cert = (
            os.path.join(self.provider_folder, f"apf-{apf_number}.crt"),
            os.path.join(self.provider_folder,
                         f"apf-{apf_number}_private_key.key"),
        )

        self.logger.info(f"Getting service to URL: {url}")

        try:
            response = requests.get(
                url,
                headers={"Content-Type": "application/json"},
                cert=cert,
                verify=os.path.join(self.provider_folder, "ca.crt"),
            )

            response.raise_for_status()

            self.logger.info("Service received successfully")
            path = os.path.join(self.provider_folder, "service_received.json")
            with open(path, 'w') as f:
                json_data = json.loads(response.text)
                json.dump(json_data, f, indent=4)
            self.logger.info(f"Service saved in {path}")

        except requests.RequestException as e:
            self.logger.error(
                f"Request to CAPIF failed: {e} - Response: {response.text}")
            raise
        except Exception as e:
            self.logger.error(
                f"Unexpected error during service getter: {e} - Response: {response.text}")
            raise

    def get_all_services(self) -> dict:
        """
        Publishes services to CAPIF and returns the published services dictionary.

        :param service_api_description_json_full_path: The full path of the service_api_description.json containing
        the endpoints to be published.
        :return: The published services dictionary that was saved in CAPIF.
        """
        self.logger.info("Starting the service publication process")

       # Load provider details
        provider_details_path = os.path.join(
            self.provider_folder, "capif_provider_details.json")
        self.logger.info(
            f"Loading provider details from {provider_details_path}")

        provider_details = self.__load_provider_api_details()
        publish_url = provider_details["publish_url"]

        chosenAPFsandAEFs = self.publish_req

        APF_api_prov_func_id = chosenAPFsandAEFs["publisher_apf_id"]

        apf_number = None
        for key, value in provider_details.items():
            if value == APF_api_prov_func_id and key.startswith("APF-"):
                apf_inter = key.split("-")[1]
                # Obtener el número del APF
                apf_number = apf_inter.split("_")[0]
                break

        if apf_number is None:
            self.logger.error(
                f"No matching APF found for publisher_apf_id: {APF_api_prov_func_id}")
            raise ValueError("Invalid publisher_apf_id")

        # Leer y modificar la descripción de la API de servicio

        # Publish services
        url = f"{self.capif_https_url}{publish_url.replace('<apfId>', APF_api_prov_func_id)}"
        cert = (
            os.path.join(self.provider_folder, f"apf-{apf_number}.crt"),
            os.path.join(self.provider_folder,
                         f"apf-{apf_number}_private_key.key"),
        )

        self.logger.info(f"Getting services to URL: {url}")

        try:
            response = requests.get(
                url,
                headers={"Content-Type": "application/json"},
                cert=cert,
                verify=os.path.join(self.provider_folder, "ca.crt"),
            )
            response.raise_for_status()
            self.logger.info("Services received successfully")

            path = os.path.join(self.provider_folder, "service_received.json")
            with open(path, 'w') as f:
                json_data = json.loads(response.text)
                json.dump(json_data, f, indent=4)
            self.logger.info(f"Services saved in {path}")

            # Save response to file

        except requests.RequestException as e:
            self.logger.error(
                f"Request to CAPIF failed: {e} - Response: {response.text}")
            raise
        except Exception as e:
            self.logger.error(
                f"Unexpected error during services reception: {e} - Response: {response.text}")
            raise

    def update_service(self) -> dict:
        """
        Publishes services to CAPIF and returns the published services dictionary.

        :param service_api_description_json_full_path: The full path of the service_api_description.json containing
        the endpoints to be published.
        :return: The published services dictionary that was saved in CAPIF.
        """
        self.logger.info("Starting the service publication process")

        # Load provider details
        # Load provider details
        provider_details_path = os.path.join(
            self.provider_folder, "capif_provider_details.json")
        self.logger.info(
            f"Loading provider details from {provider_details_path}")

        provider_details = self.__load_provider_api_details()
        publish_url = provider_details["publish_url"]

        chosenAPFsandAEFs = self.publish_req

        APF_api_prov_func_id = chosenAPFsandAEFs["publisher_apf_id"]
        AEFs_list = chosenAPFsandAEFs["publisher_aefs_ids"]

        apf_number = None
        for key, value in provider_details.items():
            if value == APF_api_prov_func_id and key.startswith("APF-"):
                apf_inter = key.split("-")[1]
                # Obtener el número del APF
                apf_number = apf_inter.split("_")[0]
                break

        if apf_number is None:
            self.logger.error(
                f"No matching APF found for publisher_apf_id: {APF_api_prov_func_id}")
            raise ValueError("Invalid publisher_apf_id")

        service_api_description_json_full_path = self.api_description_path
        # Leer y modificar la descripción de la API de servicio
        self.logger.info(
            f"Reading and modifying service API description from {service_api_description_json_full_path}")

        try:
            with open(service_api_description_json_full_path, "r") as service_file:
                data = json.load(service_file)

                # Verificamos que el número de AEFs coincide con el número de perfiles
                if len(AEFs_list) != len(data.get("aefProfiles", [])):
                    self.logger.error(
                        "The number of AEFs in publisher_aefs_ids does not match the number of profiles in aefProfiles")
                    raise ValueError(
                        "Mismatch between number of AEFs and profiles")

                # Asignamos los AEFs correspondientes
                for profile, aef_id in zip(data.get("aefProfiles", []), AEFs_list):
                    profile["aefId"] = aef_id

                self.logger.info(
                    "Service API description modified successfully")

                # Guardamos los cambios en el archivo
                with open(service_api_description_json_full_path, "w") as service_file:
                    json.dump(data, service_file, indent=4)

        except FileNotFoundError:
            self.logger.error(
                f"Service API description file not found: {service_api_description_json_full_path}")
            raise
        except json.JSONDecodeError as e:
            self.logger.error(
                f"Error decoding JSON from file {service_api_description_json_full_path}: {e}")
            raise
        except ValueError as e:
            self.logger.error(f"Error with the input data: {e}")
            raise
        api_id = "/" + chosenAPFsandAEFs["service_api_id"]
        # Publish services
        url = f"{self.capif_https_url}{publish_url.replace('<apfId>', APF_api_prov_func_id)}{api_id}"
        cert = (
            os.path.join(self.provider_folder, f"apf-{apf_number}.crt"),
            os.path.join(self.provider_folder,
                         f"apf-{apf_number}_private_key.key"),
        )

        self.logger.info(f"Publishing services to URL: {url}")

        try:
            response = requests.put(
                url,
                headers={"Content-Type": "application/json"},
                data=json.dumps(data),
                cert=cert,
                verify=os.path.join(self.provider_folder, "ca.crt"),
            )
            response.raise_for_status()
            self.logger.info("Services updated successfully")

            # Save response to file
            capif_response_text = response.text

            capif_response_json = json.loads(capif_response_text)

            # Default name if apiName is missing
            file_name = capif_response_json.get("apiName", "default_name")
            id = capif_response_json.get("apiId", "default_id")
            directory = self.provider_folder

            # Iterar sobre todos los archivos en el directorio
            for filename in os.listdir(directory):
                path = os.path.join(directory, filename)

                # Verificar si el archivo empieza con 'CAPIF-'

                if filename.startswith("CAPIF-") and id in filename:

                    # Salir del bucle si el archivo es eliminado
                    os.remove(path)
                    break

            output_path = os.path.join(
                self.provider_folder, f"CAPIF-{file_name}-{id}-api.json")

            with open(output_path, "w") as outfile:
                outfile.write(capif_response_text)
            self.logger.info(f"CAPIF response saved to {output_path}")
            output_path = os.path.join(
                self.provider_folder, "Published-Apis.json")

            # Leer el archivo existente de APIs publicados
            published_apis = {}
            if os.path.exists(output_path):
                with open(output_path, "r") as outfile:
                    published_apis = json.load(outfile)

            keys_to_remove = [key for key,
                              value in published_apis.items() if value == id]
            for key in keys_to_remove:
                del published_apis[key]
            # Agregar el nuevo API publicado
            published_apis[file_name] = id

            # Escribir el archivo actualizado de APIs publicados
            with open(output_path, "w") as outfile:
                json.dump(published_apis, outfile, indent=4)
            self.logger.info(
                f"API '{file_name}' with ID '{id}' added to Published Apis.")
            return json.loads(capif_response_text)
        except requests.RequestException as e:
            self.logger.error(
                f"Request to CAPIF failed: {e} - Response: {response.text}")
            raise
        except Exception as e:
            self.logger.error(
                f"Unexpected error during service publication: {e} - Response: {response.text}")
            raise

    def offboard_provider(self) -> None:
        """
        Offboards and deregisters the NEF (Network Exposure Function).
        """
        try:
            self.offboard_nef()
            self.__remove_files()
            self.logger.info(
                "Provider offboarded and deregistered successfully.")
        except Exception as e:
            self.logger.error(
                f"Failed to offboard and deregister Provider: {e}")
            raise

    def offboard_nef(self) -> None:
        """
        Offboards the NEF (Network Exposure Function) from CAPIF.
        """
        try:
            self.logger.info("Offboarding the provider")

            # Load CAPIF API details
            capif_api_details = self.__load_provider_api_details()
            url = f"{self.capif_https_url}api-provider-management/v1/registrations/{capif_api_details['capif_registration_id']}"

            # Define certificate paths
            cert_paths = (
                os.path.join(self.provider_folder, "amf.crt"),
                os.path.join(self.provider_folder, "AMF_private_key.key")
            )

            # Send DELETE request to offboard the provider
            response = requests.delete(
                url,
                cert=cert_paths,
                verify=os.path.join(self.provider_folder, "ca.crt")
            )

            response.raise_for_status()
            self.logger.info("Offboarding performed successfully")

        except requests.exceptions.RequestException as e:
            self.logger.error(
                f"Error offboarding Provider: {e} - Response: {response.text}")
            raise
        except Exception as e:
            self.logger.error(
                f"Unexpected error: {e} - Response: {response.text}")
            raise

    def __remove_files(self):
        self.logger.info("Removing files generated")
        try:
            folder_path = self.provider_folder

            if os.path.exists(folder_path):
                # Elimina todo el contenido dentro de la carpeta, incluyendo archivos y subcarpetas
                for root, dirs, files in os.walk(folder_path):
                    for file in files:
                        os.remove(os.path.join(root, file))
                    for dir in dirs:
                        shutil.rmtree(os.path.join(root, dir))
                os.rmdir(folder_path)
                self.logger.info(
                    f"All contents in {folder_path} removed successfully.")
            else:
                self.logger.warning(f"Folder {folder_path} does not exist.")
        except Exception as e:
            self.logger.error(f"Error during removing folder contents: {e}")
            raise

    def __load_provider_api_details(self) -> dict:
        """
        Loads NEF API details from the CAPIF provider details JSON file.

        :return: A dictionary containing NEF API details.
        :raises FileNotFoundError: If the CAPIF provider details file is not found.
        :raises json.JSONDecodeError: If there is an error decoding the JSON file.
        """
        file_path = os.path.join(self.provider_folder,
                                 "capif_provider_details.json")

        try:
            with open(file_path, "r") as file:
                return json.load(file)
        except FileNotFoundError:
            self.logger.error(f"File not found: {file_path}")
            raise
        except json.JSONDecodeError as e:
            self.logger.error(
                f"Error decoding JSON from file {file_path}: {e}")
            raise
        except Exception as e:
            self.logger.error(
                f"Unexpected error while loading NEF API details: {e}")
            raise

    def update_provider(self):
        self.certs_modifications()

        capif_postauth_info = self.__save_capif_ca_root_file_and_get_auth_token()
        capif_onboarding_url = capif_postauth_info["ccf_api_onboarding_url"]
        access_token = capif_postauth_info["access_token"]
        ccf_publish_url = capif_postauth_info["ccf_publish_url"]

        onboarding_response = self.update_onboard(
            capif_onboarding_url, access_token)

        capif_registration_id = onboarding_response["apiProvDomId"]
        self.__write_to_file(
            onboarding_response, capif_registration_id, ccf_publish_url
        )

    def certs_modifications(self):
        api_details = self.__load_provider_api_details()

        apf_count = 0
        aef_count = 0

        # Itera sobre las claves del diccionario (los campos del JSON)
        for key in api_details.keys():
            if key.startswith("APF"):
                apf_count += 1
            elif key.startswith("AEF"):
                aef_count += 1

        # Loggea los resultados (o los retorna, depende de lo que necesites)
        self.logger.info(f"Total APFs: {apf_count}, Total AEFs: {aef_count}")
        APFscertstoremove = 0
        APFscertstoadd = 0
        AEFscertstoremove = 0
        AEFscertstoadd = 0
        # Calcula la diferencia de APFs
        if apf_count != self.apfs:
            diff = apf_count - self.apfs
            if diff < 0:
                self.APFscertstoadd = abs(diff)
            else:
                APFscertstoremove = diff
        else:
            APFscertstoremove = 0
            APFscertstoadd = 0

        # Calcula la diferencia de AEFs
        if aef_count != self.aefs:
            diff = aef_count - self.aefs
            if diff < 0:
                self.AEFscertstoadd = abs(diff)
            else:
                AEFscertstoremove = diff
        else:
            AEFscertstoremove = 0
            AEFscertstoadd = 0

        # Eliminar ficheros APF en orden descendente si hay más APFs de los que debería haber
        if APFscertstoremove:
            while apf_count > self.apfs:
                # List files starting with "APF-" or "apf-" in the directory
                file_path = os.path.join(
                    self.provider_folder, f"APF-{apf_count}_private_key.key")
                os.remove(file_path)
                self.logger.info(
                    f"Removed APF file: APF-{apf_count}_private_key.key")

                file_path = os.path.join(
                    self.provider_folder, f"APF-{apf_count}_public.csr")
                os.remove(file_path)
                self.logger.info(
                    f"Removed APF file: APF-{apf_count}_public.csr")

                file_path = os.path.join(
                    self.provider_folder, f"apf-{apf_count}.crt")
                os.remove(file_path)
                self.logger.info(f"Removed APF file: apf-{apf_count}.crt")
                # Decrease the APF count
                apf_count -= 1

        # Remove AEF files in descending order if there are more AEFs than there should be
        if AEFscertstoremove:
            while aef_count > self.aefs:
                # List files starting with "AEF-" or "aef-" in the directory
                file_path = os.path.join(
                    self.provider_folder, f"AEF-{aef_count}_private_key.key")
                os.remove(file_path)
                self.logger.info(
                    f"Removed AEF file: AEF-{aef_count}_private_key.key")

                file_path = os.path.join(
                    self.provider_folder, f"AEF-{aef_count}_public.csr")
                os.remove(file_path)
                self.logger.info(
                    f"Removed AEF file: AEF-{aef_count}_public.csr")

                file_path = os.path.join(
                    self.provider_folder, f"aef-{aef_count}.crt")
                os.remove(file_path)
                self.logger.info(f"Removed AEF file: aef-{aef_count}.crt")
                # Decrease the APF count
                aef_count -= 1

    def update_onboard(self, capif_onboarding_url, access_token):
        self.logger.info(
            "Onboarding Provider to CAPIF and waiting signed certificate by giving our public keys to CAPIF")
        api_details = self.__load_provider_api_details()
        capif_id = "/" + api_details["capif_registration_id"]

        url = f"{self.capif_https_url}{capif_onboarding_url}{capif_id}"
        headers = {
            "Authorization": f"Bearer {access_token}",
            "Content-Type": "application/json",
        }

        # Crear la lista de roles sin indexar
        roles = ["AMF"]
        for n in range(1, self.aefs + 1):
            roles.append("AEF")

        for n in range(1, self.apfs + 1):
            roles.append("APF")

        # Construir el payload con los roles sin indexar
        payload = {
            "apiProvFuncs": [
                {"regInfo": {"apiProvPubKey": ""}, "apiProvFuncRole": role,
                    "apiProvFuncInfo": f"{role.lower()}"}
                for role in roles
            ],
            "apiProvDomInfo": "This is provider",
            "suppFeat": "fff",
            "failReason": "string",
            "regSec": access_token,
        }

        # Generar los roles indexados para la creación de certificados
        indexed_roles = ["AMF"] + [f"AEF-{n}" for n in range(1, self.aefs + 1)] + [
            f"APF-{n}" for n in range(1, self.apfs + 1)]

        # Recorrer cada función de proveedor de API
        for i, api_func in enumerate(payload["apiProvFuncs"]):
            # Ruta de la carpeta de proveedores
            folder_path = self.provider_folder

            # Verificar si la carpeta existe
            if os.path.exists(folder_path):
                found_key = False  # Variable para controlar si ya se encontró una clave pública

                # Recorrer los archivos en la carpeta
                for root, dirs, files in os.walk(folder_path):
                    for file_name in files:
                        if file_name.endswith(".csr"):
                            # Verificar si el archivo comienza con el rol esperado
                            role_prefix = indexed_roles[i]
                            if any(file_name.startswith(prefix) and role_prefix == prefix for prefix in [f"APF-{i+1}", f"AEF-{i+1}", "AMF"]):
                                file_path = os.path.join(root, file_name)

                                # Leer la clave pública del archivo
                                with open(file_path, "r") as csr_file:
                                    api_func["regInfo"]["apiProvPubKey"] = csr_file.read(
                                    )

                                found_key = True
                                break

                    if found_key:
                        break

                # Si no se encuentra un archivo con la clave pública, generar una nueva clave
                if not found_key:

                    public_key = self.__create_private_and_public_keys(
                        indexed_roles[i])
                    api_func["regInfo"]["apiProvPubKey"] = public_key.decode(
                        "utf-8")

        cert = (
            os.path.join(self.provider_folder, "amf.crt"),
            os.path.join(self.provider_folder, "AMF_private_key.key"),
        )

        try:
            response = requests.put(
                url,
                headers=headers,
                data=json.dumps(payload),
                cert=cert,
                verify=os.path.join(self.provider_folder, "ca.crt"),
            )

            response.raise_for_status()
            self.logger.info(
                "Provider onboarded and signed certificate obtained successfully")
            return response.json()
        except requests.exceptions.RequestException as e:
            self.logger.error(
                f"Onboarding failed: {e} - Response: {response.text}")
            raise
