from typing import List, Dict
from pydantic import BaseModel, Field
from typing import List
import requests
import os
import logging
import json
import re
import uuid
from firebase.firebase import Firebase
from feature.audience.audience_builder import AudinceBuilder
from utility.function import Function
from datetime import datetime

LOGGING_PREFIX = "[FB ADs Sync]: "
fb = Firebase(host=os.environ.get("FIREBASE_HOST"))
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class ProfileSchema(BaseModel):
    phoneNumber: List[str] = Field(default_factory=list),
    email: List[str] = Field(default_factory=list),
    facebook: Dict[str, str] = Field(default_factory=dict),

def _sanitize_error(err):
    pass

class FacebookAdsSync:
    def __init__(self, property_id:str, access_token:str=None, api_version="v24.0") :
        self.base_url = f"https://graph.facebook.com/{api_version}"
        self.client_id = os.environ.get("FB_APP_ID")
        self.client_secret = os.environ.get("FB_APP_SECRET")
        self.property_id = property_id
        self.access_token = access_token
    
    def _get_exist_access_token(self, ad_account_id):
        access_token = fb.db.reference(f"property/{self.property_id}/ad_account/facebook/access_token/use/{ad_account_id}").get()
        if access_token:
            self.access_token = access_token
        return access_token
    
    def _api_call(self, method: str, url: str, data: any = None, access_token:str = None):
        query_param_character = "?"
        if "?" in url:
            query_param_character = "&"
        url_suffix = f"{url}{query_param_character}access_token={access_token}"
        full_url = f"{self.base_url}{url_suffix}"

        try:
            response = requests.request(method, full_url, json=data)
            response.raise_for_status()
            return response.json()

        except Exception as err:
            _sanitize_error(err)

            error_data = None
            if hasattr(err, 'response') and err.response is not None:
                try:
                    error_data = err.response.json()
                except ValueError:
                    pass

            if error_data and 'error' in error_data:
                fb_error = error_data['error']
                
                if fb_error.get('message'):
                    # Logging at debug level
                    logger.debug(f"{LOGGING_PREFIX} Facebook error message was: {fb_error.get('message')}")
                    logger.debug(f"{LOGGING_PREFIX} Facebook user friendly message title was: {fb_error.get('error_user_title')}")
                    logger.debug(f"{LOGGING_PREFIX} Facebook user friendly message was: {fb_error.get('error_user_msg')}")

            data_str = json.dumps(data) if isinstance(data, (dict, list)) else str(data)
            logger.error(f"{LOGGING_PREFIX} Error in network request {method} {url} with parameters: {data_str}.")
            return None

    def _paging_results(self, url: str, access_token:str) -> list:
        data = []
        has_next = True

        while has_next:
            response = self._api_call("GET", url, access_token=access_token)
            if 'data' in response:
                data.extend(response['data'])

            # Check if paging or next link exists
            paging = response.get('paging')
            if not paging or not paging.get('next'):
                has_next = False
            else:
                next_url = paging['next']
                url = next_url

        return data
    
    def generate_long_live_access_token(self, user_id):
        try:
            url = f"{self.base_url}/oauth/access_token?grant_type=fb_exchange_token&client_id={self.client_id}\
                    &client_secret={self.client_secret}\
                    &fb_exchange_token={self.access_token}"
            respone = requests.get(url)
            data = json.loads(respone.content)
            if 'access_token' in data:
                pseudo_id = str(uuid.uuid4())
                new_access_token = data['access_token']
                self.client_secret = new_access_token
                fb.db.reference(f"users/{user_id}/ad_account/facebook/access_token/").set(new_access_token)
                return pseudo_id
            return False
        except Exception as e:
            logging.error(f"{LOGGING_PREFIX}{e}")
            return False
    
    def map_access_token(self, user_id, ad_account_ids, temp_token):
        try:
            ref = fb.db.reference(f"users/{user_id}/ad_account/access_token")
            access_token = ref.child(f"/temp/{temp_token}").get()
            if len(ad_account_ids) <0:
                return False

            for acc in ad_account_ids:
                ref.child(f"/use/{acc}").set(access_token)
            return True

        except Exception as e:
            logging.error(f"{LOGGING_PREFIX}{e}")
            return False
        
    def manageUserProfile(self, users):
        profilesList = [self.get_user_contact_data(user) for user in users]
        aggregated_phones = []
        aggregated_emails = []
        aggregated_fb_ids_by_page = {} 

        for profile in profilesList:
            if not profile:
                continue
                
            if profile['phoneNumber']:
                aggregated_phones.extend(profile['phoneNumber'])
                
            if profile['email']:
                aggregated_emails.extend(profile['email'])
                
            if profile['facebook']:
                for page_id, user_id in profile['facebook'].items():
                    if page_id not in aggregated_fb_ids_by_page:
                        aggregated_fb_ids_by_page[page_id] = []
                    aggregated_fb_ids_by_page[page_id].append(user_id)

            return {'phoneNumber': aggregated_phones,'email': aggregated_emails,'facebook': aggregated_fb_ids_by_page}

    def list_custom_audiences(self, ad_account_id):
        try:
            if not self.access_token:
                self.access_token = self._get_exist_access_token(ad_account_id)
            url = f"/act_{ad_account_id}/customaudiences?fields=name,account_id,subtype,retention_days,type&access_token={self.access_token}"
            data = self._paging_results(url)
            filter = [i for i in data if ('subtype' in i) and (i['subtype'] == 'CUSTOM')]
            return filter
        except Exception as e:
            logging.error(f"{LOGGING_PREFIX}{e}")
            return False
    
    def create_custom_audience(self, ad_account_id, name, description):
        try:
            if not self.access_token:
                self.access_token = self._get_exist_access_token(ad_account_id)
                if not self.access_token:
                    return False
            payload = {
                "name": name,
                "description": description,
                "customer_file_source": "USER_PROVIDED_ONLY",
                "subtype": "CUSTOM",
                "access_token": self.access_token
            }

            url = f"{self.base_url}/act_{ad_account_id}/customaudiences"
            response = requests.post(url, data=payload)
            response.raise_for_status()
            data = json.loads(response.content)
            return data
        except requests.exceptions.HTTPError as err:
            logging.error(f"{LOGGING_PREFIX}{err}")
            return False
        except Exception as e:
            logging.error(f"{LOGGING_PREFIX}{e}")
            return False
    
    def get_user_contact_data(self, user_pseudo_id):
        try:
            ref = fb.db.reference(f"property/{self.property_id}/profile/{user_pseudo_id}")
            user_profile = ref.get()
            phoneNumber = []
            email = []
            facebook = {}
            if "phoneNumber" in user_profile:
                phoneNumber_PGP = user_profile['phoneNumber']
                for i in phoneNumber_PGP:
                    id = phoneNumber_PGP[i]['id']
                    phoneNumber.append(id)
            
            if "email" in user_profile:
                email_PGP = user_profile['email']
                for i in email_PGP:
                    id = email_PGP[i]['id']
                    email.append(id)
            
            if "facebook" in user_profile:
                facebook = user_profile['facebook']
                for i in facebook:
                    id = facebook[i]['id']
                    page_id = facebook[i]['source']['id']
                    facebook[page_id] = id
            
            return {
                'phoneNumber': phoneNumber,
                'email': email,
                'facebook': facebook,
            }
            
        except Exception as e:
            logging.error(f"{LOGGING_PREFIX}{e}")
            return False
    
    def execute_user_to_ad_account(self, ad_account_id:str, custom_audience_id: str, profiles: dict, method:str = "APPEND"):
        
        if not self.access_token:
            self.access_token = self._get_exist_access_token(ad_account_id)
        
        MAPPING_METHOD = {
            "phoneNumber": "PHONE",
            "email": "EMAIL", 
            "facebook": "PAGEUID"
        }
        
        url = f"{self.base_url}/{custom_audience_id}/users"
        response_pack = []

        try:
            with requests.Session() as session:
                
                for profile_type, items in profiles.items():
                    data_type = MAPPING_METHOD.get(profile_type)
                    
                    if not data_type:
                        logging.warning(f"{LOGGING_PREFIX}Unknown profile type: {profile_type}")
                        continue

                    # Common payload base
                    payload = {
                        "schema": [data_type],
                        "is_raw": True,
                        "access_token": self.access_token
                    }

                    if profile_type != 'facebook':
                        payload['data'] = [[item] for item in items]
                        
                        if method != 'DELETE':
                            response = session.post(url, data=payload)
                        else:
                            response = session.delete(url, data=payload)
                        response.raise_for_status()
                        result = response.json()
                        result['type'] = data_type
                        response_pack.append(result)
                        
                    else:
                        for page_id, psids in items:
                            payload['page_ids'] = [page_id]
                            payload['data'] = [[psid] for psid in psids]
                            
                            if method != 'DELETE':
                                response = session.post(url, data=payload)
                            else:
                                response = session.delete(url, data=payload)
                            response.raise_for_status()
                            result = response.json()
                            result['type'] = data_type
                            response_pack.append(result)

            return response_pack

        except requests.exceptions.RequestException as re:
            logging.error(f"{LOGGING_PREFIX}API Request Error: {re}")
            return False
            
        except Exception as e:
            logging.error(f"{LOGGING_PREFIX}General Error: {e}")
            return False
    
    def get_executed_user_pseudo_ids(self, property_id, custom_audience_id:str='all', platform:str='all', ad_account_id:str='all', method:str='all') -> List:
        ref = fb.db.reference(f"account/{property_id}/ad_audience_sync_log")
        data = ref.get() or {}
        if not data:
            return []
        plts = [v for k, v in data.items() if platform in ('all', k)]
        auds  = [v for a in plts for k, v in (a or {}).items() if custom_audience_id in ('all', k)]
        accts = [v for a in auds for k, v in (a or {}).items() if ad_account_id in ('all', k)]
        meths = [v for a in accts for k, v in (a or {}).items() if method in ('all', k)]

        unique_ids = {
            uid 
            for m in meths 
            for log in (m or {}).get("logs", []) 
            for uid in log.get("user_pseudo_ids", [])
        }

        return list(unique_ids)
    
    def mange_execution(self):
        #Get Task
        logs = []
        base_ref = fb.db.reference(f"account/{self.property_id}/ad_audience_sync")
        base_ref_log = fb.db.reference(f"account/{self.property_id}/ad_audience_sync_log")
        audience_ids = base_ref.get(shallow=True)
        if not audience_ids:
            return False
        for audience_id in audience_ids:
            ad_account_ids = base_ref.child(f"{audience_id}/facebook").get(shallow=True)
            if not ad_account_ids:
                continue
            #Get audience profile
            audience_context = fb.db.reference(f'account/{self.property_id}/audience/{audience_id}').get()
            builder = AudinceBuilder(json=audience_context, cache=False)
            valid, message = builder._validate_json_data()
            if not valid:
                return False
            final_result = builder.get_final_audience()
            
            for ad_account_id in ad_account_ids:
                methods = base_ref.child(audience_id).child(ad_account_id).get(shallow=True)
                for method in methods:
                    execute_log = base_ref_log.child(audience_id).child(ad_account_id).child(method).child("logs").get(shallow=True)
                    status = base_ref.child(audience_id).child(ad_account_id).child(method).child("status").get()
                    custom_audience_id = base_ref.child(audience_id).child(ad_account_id).child(method).child("custom_audience_id").get()
                    
                    if status != 'active':
                        continue

                    #Start execute
                    log = []
                    #Get max key
                    max_execute_log = Function.findFirebaseMaxKey(execute_log)

                    if method == 'INCREMENTAL':
                        profiles = self.manageUserProfile(final_result)
                        log = self.execute_user_to_ad_account(ad_account_id=ad_account_id, custom_audience_id=custom_audience_id, profiles=profiles, method="APPEND")
                    elif method == 'DYNAMIC':
                        user_executed = self.get_executed_user_pseudo_ids(property_id=self.property_id, custom_audience_id=custom_audience_id, ad_account_id=ad_account_id, method=method)
                        user_delete = list(set(user_executed) - set(final_result))
                        profiles_delete = self.manageUserProfile(user_delete)
                        profiles = self.manageUserProfile(final_result)
                        self.execute_user_to_ad_account(ad_account_id=ad_account_id, custom_audience_id=custom_audience_id, profiles=profiles_delete, method="DELETE")
                        log = self.execute_user_to_ad_account(ad_account_id=ad_account_id, custom_audience_id=custom_audience_id, profiles=profiles, method="APPEND")
                    
                    if log:
                        #Log to Firebase
                        execution_time = datetime.now().isoformat()
                        base_ref_log.child(audience_id).child(ad_account_id).child(method).child("logs").child(max_execute_log).child("execution_time").set(execution_time)
                        base_ref_log.child(audience_id).child(ad_account_id).child(method).child("logs").child(max_execute_log).child("detail").set(log)
                        base_ref_log.child(audience_id).child(ad_account_id).child(method).child("logs").child(max_execute_log).child("user_pseudo_ids").set(final_result)
                        
        return logs