from collections import defaultdict from collections.abc import Sequence from anydi import singleton from fansifter_common.utils.text import is_empty from dmp.ad_accounts.enums import AdAccountPlatform from dmp.ad_accounts.repositories import AdAccountDbtRepository from dmp.adapters.tiktok.models import AdAccount from dmp.tiktok.dtos import BusinessCenterAdAccount, TikTokUserAdAccountLabel from dmp.tiktok.exceptions import TikTokAdAccountNotFoundError from dmp.tiktok.models import ( TikTokAdAccount, TikTokAdReportingConnection, TikTokUserConnection, ) from dmp.tiktok.repositories import TikTokAdAccountRepository AnyConnection = TikTokUserConnection | TikTokAdReportingConnection @singleton class TikTokAdAccountService: def __init__( self, ad_account_repository: TikTokAdAccountRepository, ad_account_dbt_repository: AdAccountDbtRepository, ) -> None: self.ad_account_repository = ad_account_repository self.ad_account_dbt_repository = ad_account_dbt_repository def fill_ad_account_empty_data[T: BusinessCenterAdAccount]( self, ad_accounts: Sequence[T] ) -> Sequence[T]: ad_accounts_dict = defaultdict(list) for ad_account in ad_accounts: ad_accounts_dict[ad_account.external_id].append(ad_account) ad_accounts_dbt = self.ad_account_dbt_repository.find_by_ids_and_platform( list(ad_accounts_dict.keys()), platform=AdAccountPlatform.TIKTOK, ) ad_reporting_account_by_id = { ad_account_dbt.id: ad_account_dbt for ad_account_dbt in ad_accounts_dbt } for external_id, _ad_accounts in ad_accounts_dict.items(): for ad_account in _ad_accounts: ad_reporting_account = ad_reporting_account_by_id.get(external_id) if ad_reporting_account is None: ad_account.name = ad_account.name or ad_account.external_id else: if ( ad_account.name == ad_account.external_id and ad_reporting_account.name ) or is_empty(ad_account.name): ad_account.name = ad_reporting_account.name ad_account.business_center_name = ( ad_account.business_center_name or ad_reporting_account.business_account_name or "" ) ad_account.campaigns_count = max( ad_account.campaigns_count, ad_reporting_account.campaigns_count or 0, ) return ad_accounts def get_ad_account(self, ad_account_id: str) -> TikTokAdAccount: ad_account = self.ad_account_repository.get(ad_account_id) if ad_account is None: raise TikTokAdAccountNotFoundError return ad_account def get_user_ad_accounts_for_assignment( self, identity_id: str, user_id: str ) -> list[TikTokUserAdAccountLabel]: ad_accounts = ( self.ad_account_repository.find_by_identity_id_and_user_id_for_assigment( identity_id, user_id ) ) return list(self.fill_ad_account_empty_data(ad_accounts)) def get_ad_accounts_for_connection( self, connection: AnyConnection, /, external_ad_accounts: Sequence[AdAccount] | Sequence[str], ) -> list[TikTokAdAccount]: external_ids: set[str] = set() api_ad_accounts_by_id: dict[str, AdAccount] = {} for api_ad_account in external_ad_accounts: if isinstance(api_ad_account, AdAccount): external_ids.add(api_ad_account.advertiser_id) api_ad_accounts_by_id[api_ad_account.advertiser_id] = api_ad_account elif isinstance(api_ad_account, str): external_ids.add(api_ad_account) ad_accounts = self.ad_account_repository.find_by_external_ids(external_ids) ad_accounts_by_id = { ad_account.external_id: ad_account for ad_account in ad_accounts } existing_ids = {ad_account.external_id for ad_account in connection.ad_accounts} updated_ids = existing_ids.intersection(external_ids) deleted_ids = existing_ids.difference(external_ids) created_ids = external_ids.difference(existing_ids) return [ *self._get_updated_ad_accounts( connection, updated_ids, deleted_ids, api_ad_accounts_by_id, ), *self._get_created_ad_accounts( connection, created_ids, api_ad_accounts_by_id, ad_accounts_by_id, ), ] @staticmethod def _set_ad_account_from_api_data( ad_account: TikTokAdAccount, api_ad_account: AdAccount ) -> None: ad_account.name = api_ad_account.name ad_account.business_center_id = api_ad_account.owner_bc_id if ad_account.business_center_name is None: ad_account.business_center_name = api_ad_account.company def _get_updated_ad_accounts( self, connection: AnyConnection, updated_ids: set[str], deleted_ids: set[str], api_ad_accounts_by_id: dict[str, AdAccount], ) -> list[TikTokAdAccount]: ad_accounts = [] for ad_account in connection.ad_accounts: if ad_account.external_id in deleted_ids: continue elif ad_account.external_id in updated_ids: api_ad_account = api_ad_accounts_by_id.get(ad_account.external_id) if api_ad_account: self._set_ad_account_from_api_data(ad_account, api_ad_account) ad_accounts.append(ad_account) return ad_accounts def _get_created_ad_accounts( self, connection: AnyConnection, created_ids: set[str], api_ad_accounts_by_id: dict[str, AdAccount], ad_accounts_by_id: dict[str, TikTokAdAccount], ) -> list[TikTokAdAccount]: ad_accounts = [] for ad_account_id in created_ids: api_ad_account = api_ad_accounts_by_id.get(ad_account_id) ad_account = ad_accounts_by_id.get(ad_account_id) if ad_account: if api_ad_account: self._set_ad_account_from_api_data(ad_account, api_ad_account) ad_accounts.append(ad_account) continue if api_ad_account: ad_accounts.append( TikTokAdAccount( external_id=api_ad_account.advertiser_id, name=api_ad_account.name, business_center_id=api_ad_account.owner_bc_id, business_center_name=api_ad_account.company, ) ) elif isinstance(connection, TikTokAdReportingConnection): ad_accounts.append(TikTokAdAccount(external_id=ad_account_id)) return ad_accounts