
from typing import Dict, List, Any, Optional, Union, TYPE_CHECKING
if TYPE_CHECKING:
    from amscrot.serviceclient import ServiceClient
from amscrot.amscrot_manager import AmSCROTManager
from amscrot.util.constants import Constants
from amscrot.util import utils
from .models import Session, Provider

class ProviderCredential:
    def __init__(self, **kwargs):
        self._attributes = kwargs

    def __getattr__(self, item):
        return self._attributes.get(item)

    def __setattr__(self, key, value):
        if key == "_attributes":
            super().__setattr__(key, value)
        else:
            self._attributes[key] = value

    def to_dict(self) -> Dict:
        return self._attributes.copy()

    def update(self, **kwargs):
        self._attributes.update(kwargs)


class Client:
    def __init__(self, *, create_service_clients: bool = False, credential_file: str = None):
        self._providers: List[Provider] = []
        self._sessions: List[Session] = []
        self._service_clients: Dict[str, "ServiceClient"] = {}
        self._credentials = {}
        self._logger = utils.get_logger()

        if create_service_clients:
            self.load_credentials(file_path=credential_file)
            self._create_service_clients_from_credentials()

    def load_credentials(self, *, file_path: str = None):
        """
        Load credentials from a YAML file.
        If file_path is not provided, defaults to ~/.amscrot/credentials.yml.
        """
        from amscrot.util.utils import load_yaml_from_file
        import os

        if file_path is None:
            file_path = "~/.amscrot/credentials.yml"
            path_expanded = os.path.expanduser(file_path)
            if not os.path.exists(path_expanded):
                # Don't overwrite existing credentials if default file is missing
                if not self._credentials:
                    self._credentials = {}
                return

        creds = load_yaml_from_file(file_path) or {}
        # Merge dicts to ProviderCredential objects
        for k, v in creds.items():
            if k in self._credentials:
                self._credentials[k].update(**v)
            else:
                self._credentials[k] = ProviderCredential(**v)

    def add_credential(self, *, profile: str, **kwargs):
        """
        Add or update a credential profile programmatically.
        """
        if profile in self._credentials:
            self._credentials[profile].update(**kwargs)
        else:
            self._credentials[profile] = ProviderCredential(**kwargs)

    def update_credential(self, *, profile: str, **kwargs):
        """
        Update an existing credential profile.
        """
        if profile not in self._credentials:
            raise ValueError(f"Profile '{profile}' not found.")
        self._credentials[profile].update(**kwargs)

    def get_credential(self, profile: str) -> "ProviderCredential":
        """
        Get a specific credential object.
        """
        return self._credentials.get(profile)

    def list_credentials(self) -> List[str]:
        """
        List available credential profiles.
        """
        return list(self._credentials.keys())

    def add_provider(self, *, label: str, type: str, profile: str = None, **kwargs) -> Provider:
        """
        Add a provider configuration.
        """
        # Load credentials if a file is specified
        if "credential_file" in kwargs:
            self.load_credentials(file_path=kwargs["credential_file"])

        attributes = {}

        if profile:
            if profile not in self._credentials:
                raise ValueError(f"Profile '{profile}' not found in loaded credentials.")
            attributes.update(self._credentials[profile].to_dict())
            attributes['profile'] = profile

        attributes.update(kwargs)

        provider = Provider(label, type, **attributes)
        self._providers.append(provider)

        # Forward to any existing sessions so providers can be added after create_session
        for session in self._sessions:
            session.add_provider(provider)

        return provider

    def add_service_client(self, service_client: "ServiceClient"):
        self._service_clients[service_client.name] = service_client
        return service_client

    def get_service_client(self, name: str = None) -> Union[Optional["ServiceClient"], List["ServiceClient"]]:
        """Retrieve a service client by name, or list all if no name given."""
        if name is None:
            return list(self._service_clients.values())
        return self._service_clients.get(name)

    def _create_service_clients_from_credentials(self):
        """Auto-create ServiceClient instances from credential entries that
        contain a ``client_type`` attribute mapping to a valid
        ``Constants.ServiceType`` name (e.g. ``ESNET_IRI``, ``NERSC_IRI``).
        """
        from amscrot.serviceclient import ServiceClient as SC

        for entry_name, cred in self._credentials.items():
            client_type_attr = getattr(cred, 'client_type', None)
            if not client_type_attr:
                continue

            # Resolve e.g. "ESNET_IRI" -> Constants.ServiceType.ESNET_IRI -> "esnet-iri"
            service_type = getattr(Constants.ServiceType, client_type_attr, None)
            if service_type is None:
                self._logger.warning(
                    f"[Client] Unknown client_type '{client_type_attr}' "
                    f"in credential entry '{entry_name}', skipping."
                )
                continue

            try:
                sc = SC.create(
                    type=service_type,
                    name=entry_name,
                    profile=entry_name,
                    credential=cred,
                )
                self._service_clients[entry_name] = sc
                self._logger.debug(
                    f"[Client] Created service client '{entry_name}' "
                    f"(type={service_type})"
                )
            except Exception as e:
                self._logger.warning(
                    f"[Client] Failed to create service client '{entry_name}' "
                    f"(type={service_type}): {e}"
                )

    def create_session(self, name: str) -> Session:
        session = Session(name=name, providers=list(self._providers), service_clients=list(self._service_clients.values()))
        self._sessions.append(session)
        return session
