from time import sleep
from azure.keyvault.secrets import SecretClient
from azure.identity import DefaultAzureCredential
from azure.core.exceptions import ResourceExistsError, ClientAuthenticationError, ResourceNotFoundError
import re
import logging

from aleph_secrets_manager.src.port import Secret, SecretsManagerPort


class AzureClientFactory:
    @staticmethod
    def create(vault_name: str) -> SecretClient:
        credential = DefaultAzureCredential()
        vault_url = f"https://{vault_name}.vault.azure.net"
        client = SecretClient(vault_url=vault_url, credential=credential)
        return client


class AzureSecretsManager(SecretsManagerPort):
    def __init__(self, client: SecretClient):
        self.client = client

    def write_secret(self, secret):
        converted_key = self._convert_write(secret.key)
        successfully_deleted = False

        while not successfully_deleted: 
            try: 
                self.client.set_secret(converted_key, secret.value)
                successfully_deleted = True
            except ResourceExistsError as e:
                code = self._extract_error_code(e.message)

                if self._is_secret_recoverable(code): 
                    self.client.begin_recover_deleted_secret(converted_key) 
                    
                logging.warning(f"🚨 Encountered error writing {secret.key}: \n{e}")
                logging.warning(f"Retrying writing of {secret.key}...")
                sleep(1) 
            except Exception as e:
                raise

    def _extract_error_code(self, error_msg: str) -> str:
        """Get the internal error code for an Azure Resource error"""
        match = re.search(r'"code":\s*"([^"]+)"', error_msg)
        code = match.group(1)
        return code
    
    def _is_secret_recoverable(self, code: str) -> bool: 
        """Checks if a secret key can be recovered from an error code"""
        return code == "ObjectIsDeletedButRecoverable"

    def read_secret(self, key):
        converted_key = self._convert_write(key)

        try:
            az_secret = self.client.get_secret(converted_key)
            return Secret(key=self._convert_read(az_secret.name), value=az_secret.value)
        except ResourceNotFoundError:
            raise ValueError(f"❌ Failed to find secret '{key}' in {self.client.vault_url}")
        except Exception as e:
            raise
    
    def delete_secret(self, key):
        converted_key = self._convert_write(key)

        try:
            deleted_secret = self.client.begin_delete_secret(converted_key).result()
        except ResourceNotFoundError: 
            raise ValueError(f"❌ Failed to delete secret '{key}' from {self.client.vault_url} as it does not exist.")
        except Exception as e:
            raise
    
    def list_all_secrets(self):
        return [
            self._convert_read(secret_properties.name) 
            for secret_properties in self.client.list_properties_of_secrets()
        ]
    
    def read_all_secrets(self):
        keys = self.list_all_secrets()
        return [self.read_secret(key) for key in keys]
    
    def _convert_read(self, key: str): 
        return key.replace('-', '_')
    
    def _convert_write(self, key: str): 
        return key.replace('_', '-')