import random
import string
import time
from dataclasses import dataclass
from datetime import datetime
from typing import Any

import pydash
from adafri.cache.lib import CacheClass
from firebase_admin.firestore import firestore

from .....utils import (
    ArrayUtils,
    BaseClass,
    DateUtils,
    DictUtils,
    RequestFields,
    get_object_model_class,
    get_request_fields,
    init_class_kwargs,
)
from .....utils.response import ApiResponse, Error, ResponseStatus, StatusCode
from .....v1 import version
from ....account import Account
from ....base.firebase_collection import FirebaseCollectionBase
from ...data.demographics import ages_data, genders_data
from ...data.devices import devices_data
from ...models.base import BaseCampaign, BaseCampaignFieldsProps
from ...models.client_customer_id import ClientCustomerId
from ...models.placement import Website, YoutubeChannel, YoutubeVideo
from ...models.user_interest import UserInterest
from ..adgroups import AdGroupDisplay
from ..ads import ImageAd, ImageAdToPublish, NativeAdsToPublish
from ..ads import ResponsiveDisplayAd as NativeAd
from .display_fields import (
    CAMPAIGN_DISPLAY_NOT_PICKABLE_FIELDS,
    CAMPAIGN_DISPLAY_PICKABLE_FIELDS,
    DISPLAY_COLLECTION,
    CampaignDisplayFields,
    CampaignDisplayFieldsProps,
)


def filter_campaign_display_request_fields(fields, default_fields = CAMPAIGN_DISPLAY_PICKABLE_FIELDS):
    request_fields = get_request_fields(fields, default_fields, [default_fields[0]])
    if request_fields is None or bool(request_fields) is False:
        request_fields = [default_fields[0]]
    return request_fields



@dataclass(init=False)
class CampaignDisplay(BaseCampaign):
    id: str
    targetedPlacements: list[Website]
    excludedPlacements: list[Website]
    websites: list[Website]
    youtubeChannels: list[YoutubeChannel]
    youtubeVideos: list[YoutubeVideo]
    images: list[ImageAdToPublish]
    imagesNative: list[NativeAdsToPublish]
    user_interest: list[UserInterest]
    target_google_search: bool
    target_search_network: bool
    target_content_network: bool
    target_partner_search_network: bool
    __baseFields = CampaignDisplayFieldsProps
    class_object = None
    ad_group_class = None;

    def __init__(self, campaign=None, **kwargs):
        _campaign = campaign;
        if type(campaign) is str:
            _campaign = {'id': campaign};
        (cls_object, keys, data_args) = init_class_kwargs(self, _campaign, CAMPAIGN_DISPLAY_PICKABLE_FIELDS, CampaignDisplayFieldsProps, DISPLAY_COLLECTION, ['id'], **kwargs)
        super().__init__(**{**data_args, "campaign": _campaign});
        for key in keys:
            if BaseCampaignFieldsProps.get(key) is None:
                website = ['targetedPlacements', 'excludedPlacements', 'websites']
                if key in website:
                    setattr(self, key, Website().fromListDict(cls_object[key]))
                elif key == 'youtubeChannels':
                    setattr(self, key, YoutubeChannel().fromListDict(cls_object[key]))
                elif key == 'youtubeVideos':    
                    setattr(self, key, YoutubeVideo().fromListDict(cls_object[key]))
                elif key == 'images':
                    setattr(self, key, ImageAdToPublish().fromListDict(cls_object[key]))
                elif key == 'imagesNative':
                    setattr(self, key, NativeAdsToPublish().fromListDict(cls_object[key]))
                elif key == 'user_interest':
                    setattr(self, key, UserInterest().fromListDict(cls_object[key]))
                else:
                    setattr(self, key, cls_object[key]) 
        self.class_object = CampaignDisplay
        self.ad_group_class = AdGroupDisplay
        self.cache = CacheClass("campaign_display", CampaignDisplay)

    def get_local_responsive_display_ads(self):
        values = [];
        print('len', len(self.imagesNative))
        for image in self.imagesNative.copy():
            values.append(image.to_responsive_display_ad(self.owner))
        return values
    def get_local_image_ads(self):
        values = [];
        for image in self.images.copy():
            values.append(image.to_image_ad(self.owner))
        return values
    def build_adgroup(self, testMode = False):
        if self.is_published() is True or self.api_version!="v0":
            return None
        campaign = CampaignDisplay.from_dict(self.to_dict())
        adgroup = AdGroupDisplay().create({"name": f"Adgroup for {self.name}"}, campaign, testMode)
        if adgroup.status == ResponseStatus.OK:
            print("adgroup migrate success", f"{adgroup.data.id} => {adgroup.data.name}")
            return AdGroupDisplay.from_dict(adgroup.data.to_dict())
        elif adgroup.status == ResponseStatus.ERROR and adgroup.error.name == "DUPLICATE_ENTITY":
            print("adgroup migrate success already exist", f"{adgroup.data.id} => {adgroup.data.name}")
            return AdGroupDisplay.from_dict(adgroup.data.to_dict())
        else:
            print("adgroup migrate error", adgroup.error.message)
        return None
    def migrate_ads(self, testMode = False) -> 'tuple[AdGroupDisplay, list[ImageAd], list[NativeAd]]':
        adgroup = self.build_adgroup(testMode);
        if adgroup is None:
            return None, [], [];
        image_ads, native_ads = self.clone_local_ads(adgroup, testMode);
        return adgroup, image_ads, native_ads
    def build_local_native_ads(self, adgroup: AdGroupDisplay, testMode = False)-> 'list[NativeAd]':
        local_responsive: list[NativeAd] = self.get_local_responsive_display_ads();
        print('local natives', len(local_responsive))
        ads_to_build = [];
        for ad in local_responsive:
            generated = NativeAd().generateAd(adgroup, ad.to_dict(), testMode);
            if generated.status == ResponseStatus.OK:
                ad_data = generated.data;
                ad_data.ad_id = 0
                ads_to_build.append(ad_data)
            else:
                print('local responsive ad error',generated.error.message)
        return ads_to_build
    def build_local_image_ads(self, adgroup: AdGroupDisplay, testMode = False)-> 'list[ImageAd]':
        local_ads: list[ImageAd] = self.get_local_image_ads();
        ads_to_build = [];
        for ad in local_ads:
            generated = ImageAd().generateAd(adgroup, ad.to_dict(), testMode);
            if generated.status == ResponseStatus.OK:
                ad_data = generated.data;
                ad_data.ad_id = 0
                ads_to_build.append(ad_data)
            else:
                print('local image ad error',generated.error.message)

        return ads_to_build

    def clone_local_ads(self, adgroup: AdGroupDisplay, testMode = False) -> 'tuple[list[ImageAd], list[NativeAd]]':
        if self._ads_cloned is True:
            return [], []
        if self.is_published() is True:
            return [], []
        # image_ads = self.build_local_image_ads(adgroup, testMode);
        image_ads = None;
        native_ads = self.build_local_native_ads(adgroup, testMode);
        # for ad in image_ads:
        operations = None
        if testMode is True:
            return image_ads, native_ads
        if bool(image_ads) is True:
            batch_add_image = ImageAd().write_batch(image_ads);
            if batch_add_image.status == ResponseStatus.ERROR:
                operations = False
            # return batch_add.data
            else:
                print('write batch image ads success')
                for ad in batch_add_image.data:
                    print("new image ad created", f"{ad.id} => {ad.name} on adgroup {adgroup.id} and campaign {self.id}")
                operations = True
        if bool(native_ads) is True:
            batch_add_native = NativeAd().write_batch(native_ads);
            if batch_add_native.status == ResponseStatus.ERROR:
                operations = False
            else:
                print('write batch native ads success')
                for ad in batch_add_native.data:
                    print("new native ad created", f"{ad.id} => {ad.name} on adgroup {adgroup.id} and campaign {self.id}")
                operations = True
        
        if operations is True:
            self.update({"_ads_cloned": True})
        return image_ads, native_ads


    def get_adgroup_class():
        return AdGroupDisplay
    def placements_to_list(self, devices: list[Website] | None = None, fields = None):
        if devices is None:
            return []
        return Website().toListDict(devices, fields)
    def channels_to_list(self, channels: list[YoutubeChannel] | None = None, fields = None):
        if channels is None:
            return []
        return YoutubeChannel().toListDict(channels, fields)
    def videos_to_list(self, videos: list[YoutubeVideo] | None = None, fields = None):
        if videos is None:
            return []
        return YoutubeVideo().toListDict(videos, fields)
    def interest_to_list(self, interest: list[UserInterest] | None = None, fields = None):
        if interest is None:
            return []
        return UserInterest().toListDict(interest, fields)
    def list_to_placements(self, placements: list[Website] | None = None, fields = None):
        if placements is None:
            return []
        return Website().toListDict(placements, fields)
    def list_to_channels(self, channels: list[YoutubeChannel] | None = None, fields = None):
        if channels is None:
            return []
        return YoutubeChannel().toListDict(channels, fields)
    def list_to_videos(self, videos: list[YoutubeVideo] | None = None, fields = None):
        if videos is None:
            return []
        return YoutubeVideo().toListDict(videos, fields)
    def list_to_interests(self, interest: list[UserInterest] | None = None, fields = None):
        if interest is None:
            return []
        return UserInterest().toListDict(interest, fields)
    @staticmethod
    def generate_model(_key_="default_value"):
        model = {};
        props = CampaignDisplayFieldsProps
        for k in DictUtils.get_keys(props):
            if k=='ages':
                model[k] = ages_data;
                continue
            if k=='genders':
                model[k] = genders_data;
                continue
            if k=='devicesTargeted':
                model[k] = devices_data;
                continue
            if k=='api_version':
                model[k] = version
                continue
            if _key_ in props[k] and props[k][_key_] is not None:
                model[k] = props[k][_key_];
        
        # Generate random campaign name
        random_suffix = ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
        model['name'] = f'Campaign Display {random_suffix}'
        model['startDate'] = datetime.now().strftime('%Y-%m-%d')
        return model;

    @staticmethod
    def from_dict(campaign: Any, db=None, collection_name=None) -> 'CampaignDisplay':
        cls_object, keys = get_object_model_class(campaign, CampaignDisplay, CampaignDisplayFieldsProps);
        _campaign = CampaignDisplay(cls_object, db=db, collection_name=collection_name)
        return _campaign
    
    def get_customer_id(self, _customerId: str | None = None):
        customer = None;
        if _customerId is None:
            default_customers = ClientCustomerId().getDefault()
            if(len(default_customers) > 0):
                customer = default_customers[0]
        else:
            customer = ClientCustomerId(_customerId).get()
        if customer is None:
            return ApiResponse(ResponseStatus.ERROR, StatusCode.status_400, None, Error("Customer not found","INVALID_REQUEST", 1))
        return ApiResponse(ResponseStatus.OK, StatusCode.status_200, customer, None)
    def generateCampaign(self, aacid: str, _customerId: str | None = None, agroup=None, data: dict | None=None):
        account = Account(aacid).get()
        if account is None:
            return ApiResponse(ResponseStatus.ERROR, StatusCode.status_400, None, Error("Account not found","INVALID_REQUEST", 1))
        
        customer_request = self.get_customer_id(_customerId);
        if customer_request.status == 'error':
            return customer_request
        customer = customer_request.data
        # token = guards(['profile'])
        # if token is None:
        #     return ApiResponse(ResponseStatus.ERROR, StatusCode.status_400, None, Error("Account not found","PERMISSION_DENIED", 1)).to_json(), StatusCode.status_400
        model = CampaignDisplay.generate_model()
        model['accountId'] = aacid
        model['clientCustomerId'] = customer.customerId
        if data is not None:
            model = DictUtils.merge_dict(data, model, True)
        create = CampaignDisplay().create(model, account, account.owner, agroup)
        if create.status == 'error':
            return create
        # print('fields', CAMPAIGN_DISPLAY_PICKABLE_FIELDS)
        values = create.data
        values = DictUtils.omit_fields(values, CAMPAIGN_DISPLAY_NOT_PICKABLE_FIELDS + ['owner', 'id_campagne', 'ad_group_id'])
        # print('values', values)
        return ApiResponse(ResponseStatus.OK, StatusCode.status_200, values, None)
    def to_dict(self, fields=None):
        base = super().to_dict(fields)
        return DictUtils.remove_none_values({
            **base,
            "targetedPlacements": Website().toListDict(self.targetedPlacements, None),
            "excludedPlacements": Website().toListDict(self.excludedPlacements, None),
            "websites": Website().toListDict(self.websites, None),
            "youtubeChannels": YoutubeChannel().toListDict(self.youtubeChannels, None),
            "youtubeVideos": YoutubeVideo().toListDict(self.youtubeVideos, None),
            "images": ImageAdToPublish().toListDict(self.images, None),
            "imagesNative": NativeAdsToPublish().toListDict(self.imagesNative, None),
            "user_interest": UserInterest().toListDict(self.user_interest, None),
            "target_google_search": self.target_google_search,
            "target_search_network": self.target_search_network,
            "target_content_network": self.target_content_network,
            "target_partner_search_network": self.target_partner_search_network
    })
    def getCampaigns(self, aacid, fields=None):
        campaigns = []
        campaigns_ = self.query(CampaignDisplay, "campaign", query_params=[{"key": "accountId", "comp": "==", "value": aacid}])
        request_fields = filter_campaign_display_request_fields(fields)
        # print('account query', account)
        for campaign in campaigns_:
            campaigns.append(campaign)
            # campaigns.append(D(campaign.to_dict(request_fields)))

        return campaigns
    @staticmethod
    def findCampaign(id, aacid=None):
        isID = False
        try:
            id = int(id)
        except Exception as e:
            isID = True
        if isID is True:
            campaign = CampaignDisplay.from_dict({"id": id}).get(CAMPAIGN_DISPLAY_PICKABLE_FIELDS)
            if campaign is None:
                return None
            return campaign 
        if id is None or aacid is None or (isinstance(id, str) and len(id) == 0) or (isinstance(aacid, str) and len(aacid)) == 0 or (isinstance(id, int) and id==0):
            return None
        campaign = CampaignDisplay(None).query(CampaignDisplay, "campaign", query_params=[{"key": "accountId", "comp": "==", "value": aacid}, {"key": "id_campagne", "comp": "==", "value": id}], first=True)
        return campaign
    def handle_migrate(self):
        adgroup, image_ads, native_ads = self.migrate_ads(False);
        if bool(adgroup) is True:
            if bool(image_ads) is True:
                print("campaign have image ads migrate", f"{len(image_ads)} image ads")
            if bool(native_ads) is True:
                print("campaign have native ads migrate", f"{len(native_ads)} native ads")
    def get(self, fields=None):
        if bool(self.id) is None:
            return None;
        campaign_cache: CampaignDisplay | None = self.cache.get(self.id);
        if bool(campaign_cache) is True:
            return campaign_cache
        request_fields = filter_campaign_display_request_fields(fields)
        doc = self.document_reference(self.id).get();
        if doc.exists is False:
            return None;
        data = {"id": doc.id, **doc.to_dict()}
        campaign = CampaignDisplay.from_dict(campaign=data, db=self.db, collection_name=self.collection_name);
        if bool(campaign) is False:
            return None
        self.cache.set(campaign.id, campaign);
        # print('created at===>', DateUtils.convert_firestore_timestamp_to_str(data['createdAt']))
        return campaign;

    def getByUserId(self,id):
        campaigns = [];
        docs = self.collection().where('owner','==',id).get();
        for doc in docs:
            data = doc.to_dict();
            data['id'] = doc.id;
            campaigns.append(CampaignDisplay.from_dict(data))
        return campaigns;

    @staticmethod
    def fields_extra():
        return [
            CampaignDisplayFields.budgetEnded,
            CampaignDisplayFields.deliveryMethod,
            CampaignDisplayFields.is_removed,
            CampaignDisplayFields.isComplete,
            CampaignDisplayFields.publish,
            CampaignDisplayFields.publishing,
            CampaignDisplayFields.publicationDate,
            CampaignDisplayFields.isEnded,
            CampaignDisplayFields.isPayed,
            CampaignDisplayFields.servingStatus,
        ]
    def normalize_response(self, _fields=None):
        fields = ['owner', 'createdBy']
        if _fields is not None:
            fields.extend(_fields)
        data = CampaignDisplay.from_dict(DictUtils.pick_fields(self.to_dict(), CAMPAIGN_DISPLAY_PICKABLE_FIELDS))
        return CampaignDisplay.from_dict(DictUtils.omit_fields(data.to_dict(), ["owner", "createdBy"]))

    def create(self, data, account: Any, uid, _adgroup=None):
        # creation_date = created_at * 1000
        # return DateUtils.from_timestamp(creation_date).isoformat()
        # try:
        data_create = DictUtils.dict_from_keys(data, CampaignDisplayFields().filtered_keys('mutable', True));
        if bool(data_create) is False:
            return ApiResponse(ResponseStatus.ERROR, StatusCode.status_400, None, Error("Invalid request","INVALID_REQUEST", 1))
        campaign_dates = DateUtils.format_campaign_date(data_create)
        if campaign_dates.status == "error":
            return campaign_dates
        model = CampaignDisplay.generate_model()
        data_create = DictUtils.merge_dict({
            **campaign_dates.data,
                "accountId": account.id,
                "owner": account.owner,
                "createdBy": f"users/{uid}"
            }, data_create,True)
        data_create = DictUtils.merge_dict(data_create, model, True)
        document_id = self.collection().document().id

        exist = self.query(CampaignDisplay, "campaign", [{"key": "name", "comp": "==", "value": data_create['name']}], True)
        if exist is not None:
            return ApiResponse(ResponseStatus.ERROR, StatusCode.status_400, None, Error(f"Campaign name {data_create['name']} already exists","INVALID_REQUEST", 1))
        model = CampaignDisplay.generate_model()
        customerId = None
        if 'clientCustomerId' in data_create:
            customerId = data_create['clientCustomerId']
        customer_request = self.get_customer_id(customerId);
        if customer_request.status == 'error':
            return customer_request
        customer = customer_request.data
        data_create['clientCustomerId'] = customer.customerId
        model['id'] = document_id
        data_create = DictUtils.merge_dict(data_create, model, True)
        create = {**data_create, 'createdAt': firestore.SERVER_TIMESTAMP}
        doc_ref = self.collection().document(document_id);
        doc_ref.set(create, True);
        data_campaign = CampaignDisplay.from_dict(create)
        campaign = data_campaign.normalize_response(CampaignDisplay.fields_extra())
        adgroup = None;
        if bool(_adgroup) is True:
            adgroup_values = {"name": "Adgroup " + campaign.name, **AdGroupDisplay.get_ad_group_props(data_create)}
            if type(_adgroup) is dict:
                adgroup_values = DictUtils.merge_dict(_adgroup, adgroup_values, True)
            adgroup = AdGroupDisplay().generateAdgroup(data_campaign, adgroup_values)
            if adgroup.status == ResponseStatus.ERROR:
                return adgroup.to_json(), adgroup.status_code
        if campaign is None:
            return ApiResponse(ResponseStatus.ERROR, StatusCode.status_400, None, Error("Failed to create campaign","INVALID_REQUEST", 1))
        # print('adgroup', adgroup.data)
        response_values = {"campaign": campaign};
        if bool(adgroup) is True:
            response_values['adgroup'] = adgroup.data
        return ApiResponse(ResponseStatus.OK, StatusCode.status_200, response_values, None);  # Return the created campaign object.  # Return the created campaign object.  # Return the created campaign object.  # Return the created campaign object.  # Return the created campaign object.  # Return the created campaign object.  # Return the created campaign object.  # Return the created campaign object.  # Return the created campaign object.  # Return the created campaign object.  # Return the created campaign object.  # Return the
        # except Exception as e:
        #     print(e)
        #     return {"error": "Exception creating campaign"};
    
    def dates_keys(self):
        return [
            "startDate",
            "endDate",
            "startDateFormattedGoogle",
            "endDateFormattedGoogle"
        ]
    def prepareAddCriterion(self, data):
        campaign_add: CampaignDisplay = self.keep_only(CampaignDisplay.from_dict(data.copy()), pydash.keys(data.copy()));
        data_add = {};
        data_final = {};
        current_targeted = self.locations_to_list(self.targetedLocations).copy();
        current_excluded = self.locations_to_list(self.excludedLocations).copy();
        current_schedules = self.schedules_to_list(self.adsSchedules).copy();
        print('current schedules', current_schedules)
        targeted_locations = [];
        excluded_locations = [];
        schedules = [];
        valid_campaign_status = ["enabled", "paused"]

        if bool(campaign_add.name) is True and bool(campaign_add.name.strip()) is True:
            data_add['name'] = campaign_add.name;
            data_final['name'] = campaign_add.name;
        if bool(campaign_add.status) is True and campaign_add.status.lower() in valid_campaign_status:
            data_add['status'] = campaign_add.status;
            data_final['status'] = campaign_add.status;
        if bool(campaign_add.startDate) or bool(campaign_add.endDate):
            print('dates', campaign_add.startDate, campaign_add.endDate)
            dates = DateUtils.format_campaign_date(data);
            if dates.status == "ok":
                dates_data = pydash.pick(dates.data, self.dates_keys());
                print('dates_data', dates.data)
                data_add = DictUtils.merge_dict(dates_data, data_add, True);
                data_final = DictUtils.merge_dict(dates_data, data_final, True);

        if bool(campaign_add.targetedLocations) is True:
            values_to_add = self.locations_to_list(campaign_add.targetedLocations);
            if bool(current_targeted) is True:
                for value in values_to_add:
                    index = pydash.find_index(current_excluded, lambda x: x['criterionId']==value['criterionId']);
                    if index == -1:
                        if pydash.find(current_targeted, lambda x: x['criterionId']==value['criterionId']) is None:
                            if pydash.find(targeted_locations, lambda x: x['criterionId']==value['criterionId']) is None:
                                targeted_locations.append(value);
            else:
                for value in values_to_add:
                    index = pydash.find_index(current_excluded, lambda x: x['criterionId']==value['criterionId']);
                    if index == -1:
                        targeted_locations.append(value);
        
        
        if bool(campaign_add.excludedLocations) is True:
            values_to_add = self.locations_to_list(campaign_add.excludedLocations);
            if bool(current_excluded) is True:
                for value in values_to_add:
                    index = pydash.find_index(current_targeted, lambda x: x['criterionId']==value['criterionId']);
                    if index == -1:
                        if pydash.find(current_excluded, lambda x: x['criterionId']==value['criterionId']) is None:
                            if pydash.find(excluded_locations, lambda x: x['criterionId']==value['criterionId']) is None:
                                excluded_locations.append(value);
            else:
                for value in values_to_add:
                    index = pydash.find_index(current_targeted, lambda x: x['criterionId']==value['criterionId']);
                    if index == -1:
                        excluded_locations.append(value);

        if bool(campaign_add.adsSchedules) is True:
            values_to_add = self.schedules_to_list(campaign_add.adsSchedules);
            if bool(current_schedules) is True:
                print('current sch', current_schedules)
                print('values to add', values_to_add)
                for value in values_to_add:
                    index = pydash.find_index(current_schedules, lambda x: x['dayEN']==value['dayEN'] and x['startHour']==value['startHour'] and x['endHour']==value['endHour']);
                    if index == -1:
                        schedules.append(value);
            else:
                schedules = values_to_add.copy();

        
        # new_target = ArrayUtils.filter(current_targeted, lambda x: pydash.find(remove_on_targeted_locations, lambda y: x['criterionId']==y['criterionId']) is None) + targeted_locations;
        # new_excluded = ArrayUtils.filter(current_excluded, lambda x: pydash.find(remove_on_excluded_locations, lambda y: x['criterionId']==y['criterionId']) is None) + excluded_locations;
        if bool(targeted_locations) is True:
            data_add['targetedLocations'] = targeted_locations;
            data_final['targetedLocations'] = current_targeted + targeted_locations;
            print('targeted locations', targeted_locations)
        if bool(excluded_locations) is True:
            data_add['excludedLocations'] = excluded_locations;
            data_final['excludedLocations'] = current_excluded + excluded_locations;
        if bool(schedules) is True:
            data_add['adsSchedules'] = schedules;
            data_final['adsSchedules'] = current_schedules + schedules;
        print('data add', data_add)
        if bool(data_add) is False:
            return ApiResponse(ResponseStatus.ERROR, StatusCode.status_304, None, Error("No data to update","INVALID_REQUEST", 1));
        data_response = {"values": data_add, "combined": data_final};
        return ApiResponse(ResponseStatus.OK, StatusCode.status_304, data_response, None);
                        
                        
    def prepareRemoveCriterion(self, data):
        campaign_remove: CampaignDisplay = self.keep_only(CampaignDisplay.from_dict(data.copy()), pydash.keys(data.copy()));
        data_remove = {};
        data_final = {};
        current_targeted = self.locations_to_list(self.targetedLocations.copy());
        current_excluded = self.locations_to_list(self.excludedLocations.copy());
        current_schedules = self.schedules_to_list(self.adsSchedules.copy());
        targeted_locations = [];
        excluded_locations = [];
        schedules = [];
        if bool(campaign_remove.targetedLocations) is True:
            values_to_remove = self.locations_to_list(campaign_remove.targetedLocations);
            if bool(current_targeted) is True:
                for value in values_to_remove:
                    index = pydash.find_index(current_targeted, lambda x: x['criterionId']==value['criterionId']);
                    if index != -1:
                        targeted_locations.append(current_targeted.copy()[index]);
        if bool(campaign_remove.excludedLocations) is True:
            values_to_remove = self.locations_to_list(campaign_remove.excludedLocations);
            if bool(current_excluded) is True:
                for value in values_to_remove:
                    index = pydash.find_index(current_excluded, lambda x: x['criterionId']==value['criterionId']);
                    if index != -1:
                        excluded_locations.append(current_excluded.copy()[index]);
        
        if bool(campaign_remove.adsSchedules) is True:
            values_to_remove = self.locations_to_list(campaign_remove.excludedLocations);
            if bool(current_schedules) is True:
                for value in values_to_remove:
                    index = pydash.find_index(current_schedules, lambda x: x['dayEN']==value['dayEN'] and x['startHour']==value['startHour'] and x['endHour']==value['endHour']);
                    if index != -1:
                        schedules.append(current_schedules.copy()[index]);
        
        if bool(targeted_locations) is True:
            data_remove['targetedLocations'] = targeted_locations;
            data_final['targetedLocations'] = ArrayUtils.filter(current_targeted, lambda x: pydash.find(targeted_locations, lambda y: x['criterionId']==y['criterionId']) is None);
        if bool(excluded_locations) is True:
            data_remove['excludedLocations'] = excluded_locations;
            data_final['excludedLocations'] = ArrayUtils.filter(current_excluded, lambda x: pydash.find(excluded_locations, lambda y: x['criterionId']==y['criterionId']) is None);
        if bool(schedules) is True:
            data_remove['adsSchedules'] = schedules;
            data_final['adsSchedules'] = ArrayUtils.filter(current_schedules, lambda x: pydash.find(schedules, lambda y: x['dayEN']==y['dayEN'] and x['startHour']==y['startHour'] and x['endHour']==y['endHour']) is None);
        data_response = {"values": data_remove, "combined": data_final};
        if bool(data_remove) is False:
            return ApiResponse(ResponseStatus.ERROR, StatusCode.status_304, None, Error("No data to remove","INVALID_REQUEST", 1));
    
        return ApiResponse(ResponseStatus.OK, StatusCode.status_304, data_response, None);
        

    def getChangedFields(self, data):
        last_value = self.to_dict();
        filtered_value = pydash.pick(data, CampaignDisplayFields().filtered_keys('editable', True));
        new_value = DictUtils.merge_dict(filtered_value, last_value, True);
        _changed_fields = DictUtils.compare_nested_objects(last_value, new_value);
        changed_fields = ArrayUtils.filter(DictUtils.get_keys(_changed_fields), lambda x: str(x).find('.') == -1)
        return changed_fields
            
    def update(self, data, silent=False):
        # try:
        data_update = {};
        changed_fields = [];
        if silent is True:
            data_update = pydash.pick(data, CampaignDisplayFields().filtered_keys('pickable', True));
        else:
            last_value = self.to_dict();
            # print('last', last_value);
            filtered_value = pydash.pick(data, CampaignDisplayFields().filtered_keys('editable', True));
            # print('last', last_value['startDate']);
            # print('data', data['startDate']);
            # print('filtered', filtered_value['startDate']);
            new_value = DictUtils.merge_dict(filtered_value, last_value, True);
            _changed_fields = DictUtils.compare_nested_objects(last_value, new_value, '', ['criterionId']);
            # print('_changed_fields', DictUtils.get_keys(_changed_fields))
            changed_fields = ArrayUtils.filter(DictUtils.get_keys(_changed_fields), lambda x: str(x).find('.') == -1)
            data_update = DictUtils.dict_from_keys(filtered_value, changed_fields);
            # print('data update', data_update)
        if bool(data_update) is False:
            return ApiResponse(ResponseStatus.OK, StatusCode.status_200, data_update, None);
        # print('data_update', data_update)
        if 'startDate' in data_update or 'endDate' in data_update:

            dates = DateUtils.format_campaign_date(new_value, False)
            if dates.status == "error":
                return dates
        
            data_update = {**data_update, **dates.data}
            changed_fields.extend(DictUtils.get_keys(data_update))
        try:
            self.document_reference().set(data_update, merge=True)
        except Exception as e:
            return ApiResponse(ResponseStatus.ERROR, StatusCode.status_500, None, Error(str(e),"INTERNAL_ERROR", 0))
        # return DictUtils.dict_from_keys(self.get(), changed_fields);
        # print('fields', fields)
        # print('data_update', data_update)
        return ApiResponse(ResponseStatus.OK, StatusCode.status_200, DictUtils.pick_fields(data_update, CAMPAIGN_DISPLAY_PICKABLE_FIELDS), None);
        # return {"status": "ok", "data": CampaignDisplay.from_dict(campaign=data_update).to_dict()};
        # return {"status": "ok", "data": DictUtils.filter_by_fields(self.get().to_dict(), fields)};
        # except Exception as e:
        #     print(e)
        #     return None;

    def remove(self, only_mark_as_removed=True):
        try:
            if self.id is None:
                return ApiResponse(ResponseStatus.ERROR, StatusCode.status_400, None, Error(f"Cannot identify Campaign with id {self.id}","INVALID_REQUEST", 1));
            if only_mark_as_removed is True:
                self.document_reference().set({"is_removed": True}, merge=True)
            else:
                self.document_reference().delete();
            return ApiResponse(ResponseStatus.OK, StatusCode.status_200, {"message": f"Campaign {self.id} deleted"}, None);
        except:
            return ApiResponse(ResponseStatus.ERROR, StatusCode.status_400, None, Error(f"An error occurated while removing authorization code with id {self.id}","INVALID_REQUEST", 1));

