import os
import re
import json
import logging
from collections import defaultdict
from datetime import datetime
from typing import Dict, Any, Optional, Union, List
from urllib.parse import urljoin
from connectors.firebase.firebase import Firebase

from ..models.base_node import BaseNode
from ..models.execution_context import ExecutionContext
from ..models.execution_result import ExecutionResult
from ..integrations.http_client import HTTPClient, HTTPClientError

logger = logging.getLogger(__name__)
fb = Firebase(host=os.environ.get("FIREBASE_HOST"))

class Facebook(BaseNode):
    def __init__(self, property_id: str, node_id: str, name: str, config: Dict[str, Any], database_logger=None):
        """
        Initialize API Call Node.
        
        Args:
            property_id: Property identifier for this node instance
            node_id: Unique identifier for this node instance
            name: Human-readable name for the node
            config: Node configuration containing HTTP request details
            database_logger: Optional database logger for node-level logging
        """
        super().__init__(property_id, node_id, name, config, database_logger)
        self.node_type = "facebook"
        self._http_client: Optional[HTTPClient] = None
        self.base_url = "https://graph.facebook.com/v24.0"
    
    def validate_config(self) -> bool:
        return True

    def _get_http_client(self) -> HTTPClient:
        """
        Get or create HTTP client instance.
        
        Returns:
            HTTPClient instance
        """
        if self._http_client is None:
            timeout = self.config.get("timeout", 3600)
            default_headers = self.config.get("default_headers", {})
            self._http_client = HTTPClient(timeout=timeout, default_headers=default_headers)
        return self._http_client
    
    def _get_page_access_token(self, page_id: str) -> str:
        http_client = self._get_http_client()
        url = f"{self.base_url}/me/accounts"
        params = {"access_token": os.environ.get('FB_TOKEN')}
        response = http_client.get(url, params=params)
        data = response.json()
        for page in data['data']:
            if str(page_id) == str(page['id']):
                return page['access_token']
    
    def _get_content(self, content_id: str) -> List[Dict[str, Any]]:
        """
        !!! IMPORTANT !!!
        This is a placeholder method for you to replace.
        It simulates fetching message content (e.g., from Firestore, a CMS, or a database)
        using the content_id from the node's configuration.

        Args:
            content_id: The identifier for the content to retrieve.
            context: The execution context, which may be needed to access databases.

        Returns:
            A list of valid LINE message objects.
        """
        print(f"Fetching content for content_id: {content_id}")
        content = fb.db.reference().child(f"account/{self.property_id}/content/facebook/{content_id}").get()
        if not content:
            raise ValueError(f"No Content {content_id} register in Database")

        if 'json' not in content:
            raise ValueError(f"No JSON object in Content {content_id}!")
    
        content_json = content['json'][0]
        if 'data' not in content_json:
            raise ValueError(f"No data object in json content {content_id}!")
        
        content_json_data = content_json['data']
        if 'message' not in content_json_data:
            raise ValueError(f"No message object in json content data {content_id}!")
        
        return content_json_data['message']
    
    def _get_psid(self, user_pseudo_id) -> List:
        social_id = fb.db.reference().child(f"account/{self.property_id}/profile/{user_pseudo_id}/facebook").get()
        if social_id:
            grouped = defaultdict(set)

            for user in social_id.values():
                page_id = user['source']['page_id']
                user_id = user['id']
                grouped[page_id].add(user_id)

            # Convert sets to lists
            grouped_final = { user_pseudo_id :{k: list(v) for k, v in grouped.items()}}
            return grouped_final
        return None
    
    def _manage_psid(self, grouped_uid) -> Dict:
        result = defaultdict(lambda: {"user_pseudo_ids": set(), "psids": set(), "mapping": {}})
        # Ensure we always iterate through a list
        if isinstance(grouped_uid, dict):
            grouped_uid = [grouped_uid]

        for entry in grouped_uid:
            if not isinstance(entry, dict):
                continue  # skip invalid entries

            for user_id, page_map in entry.items():
                if not isinstance(page_map, dict):
                    continue  # skip invalid inner data

                for page_id, uids in page_map.items():
                    if not isinstance(uids, (list, set)):
                        continue  # skip invalid UID data

                    result[page_id]["user_pseudo_ids"].add(user_id)
                    result[page_id]["psids"].update(uids)
                    for psid in uids:
                        result[page_id]["mapping"][psid] = user_id

        return {
            page_id: {
                "user_pseudo_ids": list(data["user_pseudo_ids"]),
                "psids": list(data["psids"]),
                "mapping": data["mapping"]
            }
            for page_id, data in result.items()
        }
    
    def _execute_send_message(self, config: Dict[str, Any], channel_id:str, user_pseudo_ids_context:List) -> ExecutionResult:
        """
        Handles the specific logic for sending a LINE push message.
        """
        content_id = config.get("content_id")
        channels = config.get("channels", [])

        if not content_id:
            return ExecutionResult(success=False, data={"error": "'content_id' not provided for message type."})
        if not isinstance(channels, list) or not channels:
            return ExecutionResult(success=False, data={"error": "'channels' must be a non-empty list."})

        message_objects = self._get_content(content_id)
        if not message_objects:
            return ExecutionResult(success=False,data={
                    f"{self.node_id}": {
                        "user_pseudo_ids": [],
                        "log_result": []
                    }
                }
            )

        #Prepare to send to each channel
        http_client = self._get_http_client()

        push_api_url = f"{self.base_url}/{channels[0]}/messages"
        psids = user_pseudo_ids_context['psids']
        overall_success = True

        token = self._get_page_access_token(channels[0])
        if not token:
            return {"channel": channel_id, "status": "skipped", "reason": "No token"}

        params = {"access_token": token}
        headers = {"Content-Type": "application/json"}

        logs = []
        for psid in psids:
            try:
                payload = {
                    "recipient": {"id": psid},
                    "messaging_type": "UPDATE",
                    'message': message_objects
                }

                response = http_client.post(push_api_url, headers=headers, json=payload, params=params)
                print(f"Facebook messenger push: {response.status_code} {response.content}")
                
                # Consolidate log creation
                logs.append(
                    {
                        "psid": psid,
                        "status_code": response.status_code,
                        "content": response.json()
                    }
                )
            except Exception as e:
                logs.append(
                    {
                        "psid": psid,
                        "status_code": "EXCEPTION",
                        "content": str(e)
                    }
                )
                continue
        mapping_id = user_pseudo_ids_context['mapping']

        psid_success_ids = [log['psid'] for log in logs if log['status_code'] == 200]
        psid_fail_ids = [log['psid'] for log in logs if log['status_code'] != 200]

        user_pseudo_id_success = [mapping_id[j] for j in psid_success_ids if j in mapping_id]
        user_pseudo_id_fail = [mapping_id[j] for j in psid_fail_ids if j in mapping_id]

        return {"channel": channel_id, "status": "success", "user_pseudo_ids": user_pseudo_id_success, "social_uids": psid_success_ids, "total_social_uids": len(psid_success_ids), 'total_user_pseudo_id': len(user_pseudo_id_success), "user_pseudo_ids_fail": user_pseudo_id_fail, "total_user_pseudo_ids_fail": len(user_pseudo_id_fail)}
    
    def execute(self, context: ExecutionContext) -> ExecutionResult:
        start_time = datetime.utcnow()
        if not context.data["user_pseudo_ids"]:
            logger.error(f"No audience in node {self.node_id}")
            result = ExecutionResult.error_result(
                error_message=f"No audience in node",
                node_id=self.node_id,
                data={
                    f"{self.node_id}": {
                        "user_pseudo_ids": [],
                        "log_result": []
                    }
                }
            )
            result.set_execution_time(start_time)
            return result
        print(f"Starting execution FACEBOOK. {start_time}")
        try:
            # Get Social ID
            pack_social_user_pseudo_ids = []
            no_id = []
            user_pseudo_ids_success = []
            for uid in context.data['user_pseudo_ids']:
                social_id = self._get_psid(uid)
                if not social_id:
                    no_id.append(uid)
                user_pseudo_ids_success.append(uid)
                pack_social_user_pseudo_ids.append(social_id)
            
            channels_context = self._manage_psid(pack_social_user_pseudo_ids)
            if self.config['type'] =='message':
                result_pack = []
                for channel in self.config['channels']:
                    if channel not in channels_context:
                        print(f"No audience in Facebook channel {channel}")
                    
                    result = self._execute_send_message(self.config, channel, channels_context[channel])
                    sucess_user_pseudo_ids = result['user_pseudo_ids']
                    fail_user_pseudo_ids = result['user_pseudo_ids']
                    status_groups = {
                        "completed": sucess_user_pseudo_ids,
                        "failed": fail_user_pseudo_ids,
                        "no_social_id": no_id,
                    }

                    for status, users in status_groups.items():
                        result_pack.extend([
                            {
                                "status": status,
                                "channel": result["channel"],
                                "user_pseudo_id": u,
                            }
                            for u in users
                        ])

                # Log result
                result = ExecutionResult.success_result(
                    data={
                        f"{self.node_id}": {
                            "user_pseudo_ids_no_social_id": no_id,
                            "user_pseudo_ids": sucess_user_pseudo_ids,
                            "user_pseudo_ids_failed": fail_user_pseudo_ids,
                            "log_result": result_pack
                        }
                    },
                    node_id=self.node_id
                )
                
                result.set_execution_time(start_time)
                
                logger.info(f"API call completed successfully: {result}")
                return result
        
        except Exception as e:
            print(f"Traceback for unexpected error in Facebook node: {e}")
            return False