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.aws.s3 import S3Client from dmp.adapters.meta.models import UserAdAccount from dmp.config import Settings from dmp.meta.dtos import BusinessAdAccount, MetaUserAdAccountLabel from dmp.meta.exceptions import MetaAdAccountNotFoundError from dmp.meta.models import MetaAdAccount, MetaAdReportingConnection, MetaUserConnection from dmp.meta.repositories import MetaAdAccountRepository AnyConnection = MetaUserConnection | MetaAdReportingConnection @singleton class MetaAdAccountService: def __init__( self, ad_account_repository: MetaAdAccountRepository, ad_account_dbt_repository: AdAccountDbtRepository, s3_client: S3Client, settings: Settings, ) -> None: self.ad_account_repository = ad_account_repository self.ad_account_dbt_repository = ad_account_dbt_repository self.s3_client = s3_client self.settings = settings def fill_ad_account_empty_data[T: BusinessAdAccount]( 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.META, ) 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_account_name = ( ad_reporting_account.business_account_name or ad_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) -> MetaAdAccount: ad_account = self.ad_account_repository.get(ad_account_id) if ad_account is None: raise MetaAdAccountNotFoundError return ad_account def get_user_ad_accounts_for_assignment( self, identity_id: str ) -> list[MetaUserAdAccountLabel]: ad_accounts = self.ad_account_repository.find_by_identity_id_for_assigment( identity_id ) return list(self.fill_ad_account_empty_data(ad_accounts)) def get_ad_accounts_for_connection( self, connection: AnyConnection, /, external_ad_accounts: Sequence[UserAdAccount] | Sequence[str], ) -> list[MetaAdAccount]: external_ids: set[str] = set() api_ad_accounts_by_id: dict[str, UserAdAccount] = {} for api_ad_account in external_ad_accounts: if isinstance(api_ad_account, UserAdAccount): if api_ad_account.is_eligible: external_ids.add(api_ad_account.id) api_ad_accounts_by_id[api_ad_account.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 ), ] def _get_updated_ad_accounts( self, connection: AnyConnection, updated_ids: set[str], deleted_ids: set[str], api_ad_accounts_by_id: dict[str, UserAdAccount], ) -> list[MetaAdAccount]: 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: ad_account.update_from_api_model( api_ad_account, s3_client=self.s3_client, assets_bucket_name=self.settings.campaign_assets_bucket_name, assets_cdn_path=self.settings.meta_business_accounts_cdn_assets_path, ) 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, UserAdAccount], ad_accounts_by_id: dict[str, MetaAdAccount], ) -> list[MetaAdAccount]: 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: ad_account.update_from_api_model( api_ad_account, s3_client=self.s3_client, assets_bucket_name=self.settings.campaign_assets_bucket_name, assets_cdn_path=self.settings.meta_business_accounts_cdn_assets_path, ) ad_accounts.append(ad_account) continue if api_ad_account: ad_accounts.append( MetaAdAccount.from_api_model( api_ad_account, s3_client=self.s3_client, assets_bucket_name=self.settings.campaign_assets_bucket_name, assets_cdn_path=self.settings.meta_business_accounts_cdn_assets_path, ) ) elif isinstance(connection, MetaAdReportingConnection): ad_accounts.append(MetaAdAccount(external_id=ad_account_id)) return ad_accounts