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

# %% auto 0
__all__ = ['App_API_Exception', 'get_design_by_id', 'get_design_versions', 'Design_GET_Assets',
           'get_design_source_code_by_version', 'get_all_designs', 'get_design_permissions', 'set_design_admins',
           'add_design_admin', 'DomoAppVersion', 'DomoEnterpriseApp', 'App_DownloadSourceCode', 'DomoEnterpriseApps']

# %% ../../nbs/classes/50_DomoEnterpriseApps.ipynb 2
from dataclasses import dataclass, field
import datetime as dt

from typing import List, Any
import os
from enum import Enum
import httpx


import domolibrary.client.get_data as gd

import domolibrary.client.DomoAuth as dmda
import domolibrary.client.DomoError as de
import domolibrary.utils.files as dmfi
import domolibrary.utils.convert as dmcv

import time
import zipfile

from nbdev.showdoc import patch_to

# %% ../../nbs/classes/50_DomoEnterpriseApps.ipynb 6
class App_API_Exception(de.DomoError):
    def __init__(self, res, message=None):
        super().__init__(res=res, message=message)


@gd.route_function
async def get_design_by_id(
    auth: dmda.DomoAuth,
    design_id: str,
    parts="owners,cards,versions,creator",
    debug_api: bool = False,
    debug_num_stacks_to_drop = 1,
    parent_class : str = None,
    session: httpx.AsyncClient = None
):

    url = f"https://{auth.domo_instance}.domo.com/api/apps/v1/designs/{design_id}"

    res = await gd.get_data(
        url=url,
        method="get",
        params={"parts": parts},
        auth=auth,
        debug_api=debug_api,
        num_stacks_to_drop= debug_num_stacks_to_drop,
        session= session,
        parent_class=parent_class
    )

    

    return res

# %% ../../nbs/classes/50_DomoEnterpriseApps.ipynb 8
@gd.route_function
async def get_design_versions(
    auth: dmda.DomoAuth,
    design_id,
    debug_api: bool = False,
    debug_num_stacks_to_drop=1,
    parent_class: str = None,
    session: httpx.AsyncClient = None,
):

    url = f"https://{auth.domo_instance}.domo.com/domoapps/designs/{design_id}/versions"

    res = await gd.get_data(
        url=url,
        auth=auth,
        method="get",
        debug_api=debug_api,
        num_stacks_to_drop=debug_num_stacks_to_drop,
        session=session,
        parent_class=parent_class,
    )

    if not res.is_success:
        raise App_API_Exception(res=res)

    return res

# %% ../../nbs/classes/50_DomoEnterpriseApps.ipynb 11
class Design_GET_Assets(de.DomoError):

    def __init__(self, res, design_id):

        message = f"unablto download assets for {design_id}"

        super().__init__(res=res, message=message)


@gd.route_function
async def get_design_source_code_by_version(
    auth: dmda.DomoAuth,
    design_id,
    version,
    debug_api: bool = False,
    debug_num_stacks_to_drop=1,
    parent_class: str = None,
    session: httpx.AsyncClient = None,
    download_path: str = None,  # location to download file to
    is_unpack_archive=True,
):

    url = f"http://{auth.domo_instance}.domo.com/domoapps/designs/{design_id}/versions/{version}/assets"

    res = await gd.get_data_stream(
        url=url,
        method="get",
        auth=auth,
        debug_api=debug_api,
        num_stacks_to_drop=debug_num_stacks_to_drop,
        session=session,
        parent_class=parent_class,
    )

    if not res.is_success:

        raise Design_GET_Assets(res=res, design_id=design_id)

    if download_path:

        download_path = (
            dmfi.change_extension(download_path, ".zip")
            if not is_unpack_archive
            else download_path
        )

        if debug_api:
            print(f"content downloaded to - {download_path}")

        dmfi.download_zip(
            output_folder=download_path,
            zip_bytes_content=res.response,
            is_unpack_archive=is_unpack_archive,
        )

    return res

# %% ../../nbs/classes/50_DomoEnterpriseApps.ipynb 13
@gd.route_function
async def get_all_designs(
    auth: dmda.DomoAuth,
    parts: str = "owners,creator,thumbnail,versions,cards",
    debug_api: bool = False,
    debug_num_stacks_to_drop: int = 1,
    session: httpx.AsyncClient = None,
    parent_class: str = None,
):


    url = f"https://{auth.domo_instance}.domo.com/api/apps/v1/designs"


    params = {
        "checkAdminAuthority": True,
        "deleted": False,
        "direction": "desc",
        "parts": parts,
        "search": "",

        "withPermission": "ADMIN",
    }


    res = await gd.get_data(
        url=url,
        method="get",
        params=params,
        auth=auth,

        debug_api=debug_api,

        timeout=10,
        num_stacks_to_drop=debug_num_stacks_to_drop,
        session=session,
        parent_class=parent_class,
    )

    return res

# %% ../../nbs/classes/50_DomoEnterpriseApps.ipynb 15
@gd.route_function
async def get_design_permissions(
    design_id: str,
    auth: dmda.DomoAuth,
    debug_api: bool = False,
    debug_num_stacks_to_drop: int = 1,
    session: httpx.AsyncClient = None,
    parent_class: str = None,
):

    res = await get_design_by_id(
        auth=auth,
        design_id=design_id,
        parts="owners",
        debug_api=debug_api,
        debug_num_stacks_to_drop=2,
        session=session,
        parent_class=parent_class,
    )
    res.response = res.response["owners"]
    return res


def set_design_admins(
    design_id,
    auth: dmda.DomoAuth,
    user_ids: List[str],
    debug_api: bool = False,
    return_raw: bool = False,
):
    endpoint = f"/api/apps/v1/designs/{design_id}/permissions/ADMIN"

    res = dmda.domo_api_request(
        endpoint=endpoint,
        request_type="POST",
        auth=auth,
        debug_api=debug_api,
        body=user_ids,
    )
    if return_raw:
        return res

    if not res.is_success:
        raise App_API_Exception(res=res)

    res.response = f"successfully set design_id {design_id} admins to {user_ids}"

    return res


def add_design_admin(
    design_id: str, auth: dmda.DomoAuth, user_ids: List[int], debug_api: bool = False
):
    user_ids = user_ids if isinstance(user_ids, list) else [user_ids]

    res = get_design_permissions(design_id=design_id, auth=auth, debug_api=debug_api)

    user_ids = list(set([owner["id"] for owner in res.response] + user_ids))

    return set_design_admins(
        design_id=design_id, auth=auth, debug_api=debug_api, user_ids=user_ids
    )

# %% ../../nbs/classes/50_DomoEnterpriseApps.ipynb 18
@dataclass
class DomoAppVersion:
    auth: dmda.DomoAuth = field(repr=False)
    id: str
    design_id: str
    version: str
    draft: bool
    is_client_code_enabled: bool = False
    collection_mapping: List[Any] = None

    @classmethod
    def _from_api(cls, obj, auth):
        return cls(
            auth=auth,
            id=obj["id"],
            design_id=obj["designId"],
            version=obj["version"],
            draft=obj["draft"],
            is_client_code_enabled=obj.get("flags", {}).get(
                "client-code-enabled", False
            ),
            collection_mapping=obj["collectionsMapping"],
        )


@dataclass
class DomoEnterpriseApp:
    auth: dmda.DomoAuth = field(repr=False)
    id: str
    name: str
    owner: dmdu.DomoUser
    created_dt: dt.datetime
    lastmodified_dt: dt.datetime
    versions: List[str]
    current_version: str

    is_client_code_enabled: bool = False

    referencing_cards: List[dict] = None
    ddx_collection_source_code: Any = None

    @classmethod
    def _from_json(cls, obj, auth: dmda.DomoAuth, debug_api: bool = False):

        domo_user = None

        try:
            if obj.get("owner"):
                domo_user = dmdu.DomoUser.get_by_id(
                    user_id=obj["owner"], auth=auth, debug_api=debug_api
                )

        except dmdu.User_API_Exception as e:
            print(e)

        ea = cls(
            auth=auth,
            id=obj["id"],
            name=obj["name"],
            owner=domo_user,
            created_dt=obj["createdDate"],
            lastmodified_dt=obj["updatedDate"],
            versions=obj["versions"],
            current_version=obj["latestVersion"],
        )

        if obj.get("referencingCards", None):
            import mbison.feature.cards as dmac

            ea.referencing_cards = [
                dmac.DomoCard.get_by_id(card_id=card["id"], auth=auth)
                for card in obj.get("referencingCards")
            ]

        if obj.get("versions"):
            ea.versions = [
                DomoAppVersion._from_api(obj, auth) for obj in obj.get("versions")
            ]
            ea.is_client_code_enabled = all(
                version.is_client_code_enabled for version in ea.versions
            )

        return ea

    @classmethod
    def get_by_id(
        cls,
        design_id,
        auth: dmda.DomoAuth,
        debug_api: bool = False,
        return_raw: bool = False,
    ):
        res = get_design_by_id(auth=auth, design_id=design_id, debug_api=debug_api)

        if return_raw:
            return res

        return cls._from_json(obj=res.response, auth=auth, debug_api=debug_api)

    
    def get_versions(self, debug_api: bool = False, return_raw: bool = False):

        res = get_design_versions(
            auth=self.auth, design_id=self.id, debug_api=debug_api
        )
        if return_raw:
            return res

        self.versions = res.response
        return self.versions

    def share(self, domo_users: List[Any] = None, debug_api: bool = False):
        user_ids = (
            [domo_user.id for domo_user in domo_users]
            if domo_users
            else [self.auth.who_am_i()["id"]]
        )
        return add_design_admin(design_id=self.id, auth=self.auth, user_ids=user_ids, debug_api= debug_api)

# %% ../../nbs/classes/50_DomoEnterpriseApps.ipynb 20
class App_DownloadSourceCode(dmda.Class_Exception):
    def __init__(self, cls, auth, message):
        super().__init__(cls = cls, auth = auth, message = message)

@patch_to(DomoEnterpriseApp)
def _get_source_code_enterprise_app(
        self,
        version: str = None,
        debug_api: bool = False,
        download_folder="./EXPORT/",
        file_name=None,
        try_auto_share: bool = False,
        is_unzip = True
    ):
        version = version or self.current_version

        file_path = f"{self.id}-{dmut.convert_str_to_snake_case(self.name, is_only_alphanumeric = True)}/{version }/"

        file_name = file_name or os.path.join(file_path, "archive.zip")
    
        file_name = dmut.change_suffix(file_name, ".zip")

        download_path = os.path.join(download_folder, file_name)

        loop_retry = 0
        res = None
        while loop_retry<=1 and not res:
            try:
                res =  get_design_source_code_by_version(
                    auth=self.auth,
                    download_path=download_path,
                    design_id=self.id,
                    version=version,
                    debug_api=debug_api,
                )
            
            except Design_GET_Assets as e:
                if try_auto_share:
                    self.share(debug_api = debug_api)
                    time.sleep(2)

                loop_retry += 1

                if loop_retry > 1:
                    raise App_DownloadSourceCode(cls = self, auth = self.auth, message = e)
        
        if not is_unzip:
            return res
        
        
        with zipfile.ZipFile(download_path, 'r') as f:
            f.extractall(os.path.join(download_folder,file_path))

        


@patch_to(DomoEnterpriseApp)
def _get_ddx_source_code(
    self,
    debug_api: bool = False,
    try_auto_share: bool = False,
    file_name : str = None,
    download_folder: str = "./EXPORT/",
):
    """
    identifies the collection that stores the source code for a ddx brick

    note card metadata includes "domoapps" which identifies the datastore that feeds an app.
    the one or many collections can be associated with a datastore / app
    for ddx, the source code is stored in the collection 'ddx_app_client_code'
    """

    import mbison.feature.cards as dmdc

    if not self.referencing_cards:
        raise App_DownloadSourceCode(cls = self, auth = self.auth, message= f"unable to download source code for design {self.id} - no referencing_cards attached to this object, rerun with referencing_cards")
    
    try:
        return [
                domo_card.download_source_code(
                    debug_api=debug_api,
                    try_auto_share=try_auto_share,
                    download_folder=download_folder,
                    file_name = file_name
                )
                for domo_card in self.referencing_cards
            ]

    except dmdc.Card_DownloadSourceCode as e: 
        if not try_auto_share:
            raise App_DownloadSourceCode(cls = self, auth = self.auth, message= f"unable to download source code for design {self.id} - {e} try rerunning with try_auto_share = True")
   
        raise App_DownloadSourceCode(cls = self, auth = self.auth, message= f"unable to download source code for design {self.id} - {e}")


@patch_to(DomoEnterpriseApp)
def download_source_code(
    self,
    debug_api: bool = False,
    try_auto_share: bool = False,
    download_folder: str = "./EXPORT/",
    version: str = None,
    file_name: str = None,
):

    try:
        return self._get_source_code_enterprise_app(
            version=version,
            debug_api=debug_api,
            download_folder=download_folder,
            file_name=file_name,
            try_auto_share = try_auto_share
        )

    except App_DownloadSourceCode as e:
        if not try_auto_share:
            message = f"unble to download design {self.id} try rerunning with try_auto_share = True to share the app with authenticated user"
            raise App_DownloadSourceCode(cls = self,
                                        auth = self.auth, 
                                        message = message)

        if not self.is_client_code_enabled:
            message = "is_client_code_enabled (code storage in appdb) is False AND unable to download design {self.id} -- this may be a published app (and therefore unable to access source code)"
            raise App_DownloadSourceCode(cls = self,
                                        auth = self.auth, 
                                        message = message)

        
        print(e)

        return self._get_ddx_source_code(
                debug_api=debug_api,
                try_auto_share=try_auto_share,
                download_folder=download_folder,
                file_name=file_name,
            )

# %% ../../nbs/classes/50_DomoEnterpriseApps.ipynb 22
@dataclass
class DomoEnterpriseApps:
    auth: dmda.DomoAuth = field(repr=False)
    enterprise_apps: List[DomoEnterpriseApp] = None

    failed_downloads: List[DomoEnterpriseApp] = None
    success_downloads : List[DomoEnterpriseApp] = None


    def get_apps(self, debug_api: bool = False, return_raw: bool = False):
        res = get_all_designs(auth=self.auth, debug_api=debug_api)

        if return_raw:
            return res

        self.enterprise_apps = [
            DomoEnterpriseApp._from_json(auth=self.auth, obj=obj)
            for obj in res.response
        ]
        return self.enterprise_apps
    
    def get_all_app_source_code(self, try_auto_share : bool = False, debug_api: bool = False, download_folder= './EXPORTS/'):

        self.failed_downloads = []
        self.success_downloads = []

        if not self.enterprise_apps:
            self.get_apps(debug_api = debug_api)

        for app in self.enterprise_apps:
            try:
                app.download_source_code(try_auto_share= try_auto_share, debug_api = debug_api, download_folder = download_folder)
                self.success_downloads.append(app)
            
            except App_DownloadSourceCode as e:
                print(e)
                self.failed_downloads.append({'error' : e, 'app': app})


