# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/classes/50_DomoAccount_Default.ipynb.

# %% auto 0
__all__ = ['Account_CanIModify', 'UpsertAccount_MatchCriteria', 'DomoAccount_Default', 'DomoAccounConfig_MissingFields']

# %% ../../nbs/classes/50_DomoAccount_Default.ipynb 3
from domolibrary.routes.account import (
    ShareAccount_V1_AccessLevel,
    ShareAccount_V2_AccessLevel,
    ShareAccount,
    GetAccount_NoMatch,
    ShareAccount_Error,
    ShareAccount_Error_AlreadyShared,
    DeleteAccount_Error,
)

from domolibrary.classes.DomoAccount_Config import (
    AccountConfig_UsesOauth,
    AccountConfig_ProviderTypeNotDefined,
    DomoAccount_Config,
    AccountConfig,
)

# %% ../../nbs/classes/50_DomoAccount_Default.ipynb 4
from dataclasses import dataclass, field
from typing import Any, List

import httpx
import datetime as dt

from nbdev.showdoc import patch_to

import domolibrary.utils.convert as cd
import domolibrary.utils.DictDot as util_dd
import domolibrary.client.DomoAuth as dmda
import domolibrary.client.DomoError as dmde
import domolibrary.routes.account as account_routes

import domolibrary.utils.chunk_execution as dmce

# %% ../../nbs/classes/50_DomoAccount_Default.ipynb 7
class Account_CanIModify(dmde.ClassError):
    def __init__(self, account_id, domo_instance):
        super().__init__(
            message="`DomoAccount.is_admin_summary` must be `False` to proceed.  Either set the value explicity, or retrieve the account instance using `DomoAccount.get_by_id()`",
            domo_instance=domo_instance,
            entity_id=account_id,
        )


class UpsertAccount_MatchCriteria(dmde.ClassError):
    def __init__(self, domo_instance):
        super().__init__(
            message="must pass an account_id or account_name to UPSERT",
            domo_instance=domo_instance,
        )

# %% ../../nbs/classes/50_DomoAccount_Default.ipynb 9
@dataclass
class DomoAccount_Default:
    id: int
    auth: dmda.DomoAuth = field(repr=False)

    name: str = None
    data_provider_type: str = None

    created_dt: dt.datetime = None
    modified_dt: dt.datetime = None

    config: DomoAccount_Config = None

    owners: List[Any] = None  # DomoUser or DomoGroup

    is_admin_summary: bool = True

    @classmethod
    def _default_from_json(
        cls,
        obj: dict,
        is_admin_summary: bool = True,
        auth: dmda.DomoAuth = None,
        **kwargs
    ):

        """converts data_v1_accounts API response into an accounts class object"""

        dd = util_dd.DictDot(obj)

        return cls(
            id=dd.id or dd.databaseId,
            name=dd.displayName,
            data_provider_type=dd.dataProviderId or dd.dataProviderType,
            created_dt=cd.convert_epoch_millisecond_to_datetime(
                dd.createdAt or dd.createDate
            ),
            modified_dt=cd.convert_epoch_millisecond_to_datetime(
                dd.modifiedAt or dd.lastModified
            ),
            auth=auth,
            is_admin_summary=is_admin_summary,
            owners=dd.owners,
            **kwargs
        )

    @classmethod
    def _class_from_json(
        cls,
        obj: dict,
        is_admin_summary: bool = True,
        auth: dmda.DomoAuth = None,
        **kwargs
    ):
        return cls._default_from_json(
            obj=obj, is_admin_summary=is_admin_summary, auth=auth
        )

    @classmethod
    def _from_json(
        cls,
        obj: dict,
        is_admin_summary: bool = True,
        auth: dmda.DomoAuth = None,
        **kwargs
    ):
        return cls._class_from_json(
            obj=obj, is_admin_summary=is_admin_summary, auth=auth, **kwargs
        )

# %% ../../nbs/classes/50_DomoAccount_Default.ipynb 11
class DomoAccounConfig_MissingFields(dmde.ClassError):
    def __init__(self, domo_instance, missing_keys, account_id):
        super().__init__(
            domo_instance=domo_instance,
            message=f"{account_id} config class definition is missing the following keys - {', '.join(missing_keys)} extend the AccountConfig",
        )


@patch_to(DomoAccount_Default)
def _test_missing_keys(self, res_obj, config_obj):
    return [r_key for r_key in res_obj.keys() if r_key not in config_obj.keys()]


@patch_to(DomoAccount_Default)
async def _get_config(
    self: DomoAccount_Default,
    session=None,
    return_raw: bool = False,
    debug_api: bool = None,
    auth: dmda.DomoAuth = None,
    debug_num_stacks_to_drop=2,
    is_suppress_no_config: bool = False,  # can be used to suppress cases where the config is not defined, either because the account_config is OAuth, and therefore not stored in Domo OR because the AccountConfig class doesn't cover the data_type
):
    if not self.data_provider_type:
        res = await account_routes.get_account_from_id(
            auth=self.auth,
            account_id=self.id,
            session=session,
            debug_api=debug_api,
            parent_class=self.__class__.__name__,
            debug_num_stacks_to_drop=debug_num_stacks_to_drop,
        )

        self.data_provider_type = res.response["dataProviderType"]

    res = await account_routes.get_account_config(
        auth=auth or self.auth,
        account_id=self.id,
        session=session,
        debug_api=debug_api,
        data_provider_type=self.data_provider_type,
        parent_class=self.__class__.__name__,
        debug_num_stacks_to_drop=debug_num_stacks_to_drop,
    )

    if return_raw:
        return res

    config_fn = AccountConfig(self.data_provider_type).value

    if not is_suppress_no_config and not config_fn.is_defined_config:
        raise config_fn._associated_exception(self.data_provider_type)

    self.config = config_fn._from_json(res.response)

    if self.config and self.config.to_json() != {}:
        if not res.response:
            print(self.data_provider_type, "no response")

        if not self.config.to_json():
            print(
                self.id,
                self.data_provider_type,
                "no config",
                self.config.to_json(),
                res.response,
            )

        self._test_missing_keys(res_obj=res.response, config_obj=self.config.to_json())

    return self.config

# %% ../../nbs/classes/50_DomoAccount_Default.ipynb 14
@patch_to(DomoAccount_Default, cls_method=True)
async def get_by_id(
    cls,
    auth: dmda.DomoAuth,
    account_id: int,
    is_suppress_no_config: bool = True,
    session: httpx.AsyncClient = None,
    return_raw: bool = False,
    debug_api: bool = False,
    debug_num_stacks_to_drop=2,
    **kwargs
):
    """retrieves account metadata and attempts to retrieve config"""

    res = await account_routes.get_account_from_id(
        auth=auth,
        account_id=account_id,
        session=session,
        debug_api=debug_api,
        parent_class=cls.__name__,
        debug_num_stacks_to_drop=debug_num_stacks_to_drop,
    )

    if return_raw:
        return res

    acc = cls._from_json(obj=res.response, auth=auth, is_admin_summary=False, **kwargs)

    await acc._get_config(
        session=session,
        debug_api=debug_api,
        debug_num_stacks_to_drop=debug_num_stacks_to_drop + 1,
        is_suppress_no_config=is_suppress_no_config,
    )

    return acc

# %% ../../nbs/classes/50_DomoAccount_Default.ipynb 20
@patch_to(DomoAccount_Default, cls_method=True)
async def create_account(
    cls: DomoAccount_Default,
    account_name: str,
    config: DomoAccount_Config,
    auth: dmda.DomoAuth,
    debug_api: bool = False,
    return_raw: bool = False,
    session: httpx.AsyncClient = None,
):
    body = account_routes.generate_create_body(account_name=account_name, config=config)

    res = await account_routes.create_account(
        auth=auth, config_body=body, debug_api=debug_api, session=session
    )

    if return_raw:
        return res

    return await cls.get_by_id(auth=auth, account_id=res.response.get("id"))

# %% ../../nbs/classes/50_DomoAccount_Default.ipynb 22
@patch_to(DomoAccount_Default)
async def update_config(
    self: DomoAccount_Default,
    auth: dmda.DomoAuth = None,
    debug_api: bool = False,
    config: DomoAccount_Config = None,
    is_suppress_no_config=False,
    debug_num_stacks_to_drop=2,
    session: httpx.AsyncClient = None,
    return_raw: bool = False,
    is_update_config: bool = False
):
    auth = auth or self.auth
    config = config or self.config

    res = await account_routes.update_account_config(
        auth=auth,
        account_id=self.id,
        config_body=config.to_json(),
        debug_api=debug_api,
        session=session,
    )

    if return_raw:
        return res

    if not res.is_success and self.is_admin_summary:
        raise Account_CanIModify(account_id=self.id, domo_instance=auth.domo_instance)

    if not is_update_config: 
        return self
     
    await self._get_config(
        debug_api=debug_api,
        debug_num_stacks_to_drop=debug_num_stacks_to_drop + 1,
        is_suppress_no_config=is_suppress_no_config,
    )

    return self

# %% ../../nbs/classes/50_DomoAccount_Default.ipynb 27
@patch_to(DomoAccount_Default)
async def update_name(
    self: DomoAccount_Default,
    account_name: str = None,
    auth: dmda.DomoAuth = None,
    debug_api: bool = False,
    session: httpx.AsyncClient = None,
    return_raw: bool = False,
):
    auth = auth or self.auth

    res = await account_routes.update_account_name(
        auth=auth,
        account_id=self.id,
        account_name=account_name or self.name,
        debug_api=debug_api,
        session=session,
    )

    if return_raw:
        return res

    if not res.is_success and self.is_admin_summary:
        raise Account_CanIModify(account_id=self.id, domo_instance=auth.domo_instance)

    await self.get_by_id(auth=auth, account_id=self.id)

    return self

# %% ../../nbs/classes/50_DomoAccount_Default.ipynb 31
@patch_to(DomoAccount_Default)
async def delete_account(
    self: DomoAccount_Default,
    auth: dmda.DomoAuth = None,
    debug_api: bool = False,
    session: httpx.AsyncClient = None,
    debug_num_stacks_to_drop=2,
    parent_class=None,
):
    auth = auth or self.auth

    res = await account_routes.delete_account(
        auth=auth,
        account_id=self.id,
        debug_api=debug_api,
        session=session,
        debug_num_stacks_to_drop=debug_num_stacks_to_drop,
        parent_class=parent_class,
    )

    if not res.is_success and self.is_admin_summary:
        raise Account_CanIModify(account_id=self.id, domo_instance=auth.domo_instance)

    return res

# %% ../../nbs/classes/50_DomoAccount_Default.ipynb 33
@patch_to(DomoAccount_Default)
async def is_feature_accountsv2_enabled(
    self: DomoAccount_Default, auth: dmda.DomoFullAuth = None, return_raw: bool = False
):
    """uses bootstrap class to test if the auth object refers to an instancce that has the account-v2 feature switch enabled"""
    import domolibrary.classes.DomoBootstrap as dmbs

    auth = auth or self.auth

    domo_bsr = dmbs.DomoBootstrap(auth=auth)

    try:
        is_v2 = await domo_bsr.is_feature_accountsv2_enabled()
        return 1 if is_v2 else 0

    except dmbs.InvalidAuthTypeError as e:
        print(
            f"Warning - unable to test if accounts_v2 feature is enabled in {auth.domo_instance}, recommend pass FullAuth"
        )
        return -1

# %% ../../nbs/classes/50_DomoAccount_Default.ipynb 36
@patch_to(DomoAccount_Default)
async def _share_v2(
    self: DomoAccount_Default,
    auth: dmda.DomoAuth = None,
    user_id=None,
    group_id=None,
    access_level: ShareAccount = ShareAccount_V2_AccessLevel.CAN_VIEW,
    debug_api: bool = False,
    session: httpx.AsyncClient = None,
    debug_num_stacks_to_drop=2,
    return_raw: bool = False,
    is_suppress_already_shared: bool = True,
):
    auth = auth or self.auth

    is_v2 = await self.is_feature_accountsv2_enabled(auth=auth)

    if is_v2 == 0:
        raise account_routes.ShareAccount_Error(
            account_id=self.id,
            response="accounts_v2 feature not enabled, use v1 share method",
            domo_instance=auth.domo_instance,
            function_name="_share_v2",
            parent_class=self.__class__.__name__,
            status=None,
        )

    share_payload = account_routes.generate_share_account_payload_v2(
        user_id=user_id,
        group_id=group_id,
        access_level=access_level,
    )
    try:
        res = await account_routes.share_account_v2(
            auth=auth,
            account_id=self.id,
            share_payload=share_payload,
            debug_api=debug_api,
            session=session,
            parent_class=self.__class__.__name__,
            debug_num_stacks_to_drop=debug_num_stacks_to_drop,
        )

        if return_raw:
            return res

        return f"shared {self.id} - {self.name} with {group_id or user_id}"

    except ShareAccount_Error_AlreadyShared as e:
        if not is_suppress_already_shared:
            raise e

        return f"already shared {self.id} - {self.name} with {group_id or user_id}"

# %% ../../nbs/classes/50_DomoAccount_Default.ipynb 39
@patch_to(DomoAccount_Default)
async def _share_v1(
    self: DomoAccount_Default,
    auth: dmda.DomoAuth = None,
    user_id=None,
    access_level: ShareAccount = ShareAccount_V1_AccessLevel.CAN_VIEW,
    debug_api: bool = False,
    session: httpx.AsyncClient = None,
    debug_num_stacks_to_drop=2,
    return_raw: bool = False,
    is_suppress_already_shared: bool = True,
):
    auth = auth or self.auth

    is_v2 = await self.is_feature_accountsv2_enabled(auth=auth)

    if is_v2 == 1:
        raise account_routes.ShareAccount_Error(
            account_id=self.id,
            response="accounts_v2 feature enabled, use v2 share method",
            domo_instance=auth.domo_instance,
            function_name="_share_v2",
            parent_class=self.__class__.__name__,
            status=None,
        )

    share_payload = account_routes.generate_share_account_payload_v1(
        user_id=user_id,
        access_level=access_level,
    )
    try:
        res = await account_routes.share_account_v1(
            auth=auth,
            account_id=self.id,
            share_payload=share_payload,
            debug_api=debug_api,
            session=session,
            parent_class=self.__class__.__name__,
            debug_num_stacks_to_drop=debug_num_stacks_to_drop,
        )

        if return_raw:
            return res

        return f"shared {self.id} - {self.name} with { user_id}"

    except ShareAccount_Error_AlreadyShared as e:
        if is_suppress_already_shared:
            return f"already shared {self.id} - {self.name} with { user_id}"

        else:
            raise e

# %% ../../nbs/classes/50_DomoAccount_Default.ipynb 42
@patch_to(DomoAccount_Default)
async def share(
    self: DomoAccount_Default,
    user_id=None,
    group_id=None,
    domo_user=None,
    domo_group=None,
    auth: dmda.DomoAuth = None,
    access_level: ShareAccount = None,  # will default to Read
    is_suppress_already_shared: bool = True,
    debug_api: bool = False,
    debug_num_stacks_to_drop: int = 3,
    debug_prn: bool = False,
    session: httpx.AsyncClient = None,
):
    auth = auth or self.auth
    is_v2 = await self.is_feature_accountsv2_enabled(auth=auth)

    user_id = user_id or (domo_user and domo_user.id)

    debug = {"is_accounts_v2": is_v2}

    res = None

    if is_v2 == 1:
        group_id = group_id or (domo_group and domo_group.id)

        debug.update(
            {
                "user_id*": user_id,
                "group_id": group_id,
            }
        )

        if debug_prn:
            print(debug)

        res = await self._share_v2(
            auth=auth,
            user_id=user_id,
            group_id=group_id,
            debug_api=debug_api,
            debug_num_stacks_to_drop=debug_num_stacks_to_drop,
            session=session,
            is_suppress_already_shared=is_suppress_already_shared,
        )

    elif is_v2 == 0:
        user_ids = [user_id]

        if group_id:
            import domolibrary.classes.DomoGroup as dmdg

            domo_group = await dmdg.DomoGroup.get_by_id(group_id=group_id, auth=auth)

        group_id = group_id or domo_group.id
        domo_users = await domo_group.Membership.get_members()
        user_ids = [domo_user.id for domo_user in domo_users]

        debug.update({"group_id": group_id, "user_ids": user_ids})

        if debug_prn:
            print(debug)

        res = await dmce.gather_with_concurrency(
            *[
                self._share_v1(
                    auth=auth,
                    user_id=user_id,
                    debug_api=debug_api,
                    debug_num_stacks_to_drop=debug_num_stacks_to_drop,
                    session=session,
                    is_suppress_already_shared=is_suppress_already_shared,
                )
                for user_id in user_ids
            ],
            n=10,
        )

    return res
