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 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 LINEAPI(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 = "line_api"
        self._http_client: Optional[HTTPClient] = None
        self.base_url = "https://api.line.me/v2/bot"
    
    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_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/line/{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}!")
        
        return content['json']
    
    def _get_line_uid(self, user_pseudo_id) -> List:
        social_id = fb.db.reference().child(f"account/{self.property_id}/profile/{user_pseudo_id}/line").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_line_uid(self, grouped_uid) -> Dict:
        result = defaultdict(lambda: {"user_pseudo_ids": set(), "line_uid": 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]["line_uid"].update(uids)
                    for uid in uids:
                        result[page_id]["mapping"][uid] = user_id

        # Convert sets to lists for JSON-serializable result
        return {
            page_id: {
                "user_pseudo_ids": list(data["user_pseudo_ids"]),
                "line_uid": list(data["line_uid"]),
                "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", [])

        print(f"user_pseudo_ids_context: {user_pseudo_ids_context}")

        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}/message/push" if len(user_pseudo_ids_context['line_uid']) <= 1 else f"{self.base_url}/message/multicast"
        push_api_url = f"{self.base_url}/message/push"
        line_uid = user_pseudo_ids_context['line_uid'][0] if len(user_pseudo_ids_context['line_uid']) <= 1 else user_pseudo_ids_context['line_uid']
        overall_success = True

        token = os.environ.get(f"LINE_{channel_id}", None)
        if not token:
            return {"channel": channel_id, "status": "skipped", "reason": "No token"}

        # 4. Prepare the API request payload and headers
        headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
        

        # 5. Make the API call
        logs = []

        try:
            for id in line_uid:
                payload = {"to": id, "messages": message_objects}
                
                response = http_client.post(push_api_url, headers=headers, json=payload)
                
                log_entry = {
                    "line_uid": id,
                    "status_code": response.status_code,
                    "content": response.json()
                }
                logs.append(log_entry)
                print(response.status_code, response.json())
                
        except HTTPClientError as e:
            print(f"A general HTTP error occurred: {e}")

        mapping_id = user_pseudo_ids_context['mapping']

        line_success_ids = [log['line_uid'] for log in logs if log['status_code'] == 200]
        line_fail_ids = [log['line_uid'] for log in logs if log['status_code'] != 200]

        user_pseudo_id_success = [mapping_id[j] for j in line_success_ids if j in mapping_id]
        user_pseudo_id_fail = [mapping_id[j] for j in line_fail_ids if j in mapping_id]
        
        return {"channel": channel_id, "status": "success", "user_pseudo_ids": user_pseudo_id_success, "social_uids": line_success_ids, "total_social_uids": len(line_success_ids), 'total_user_pseudo_ids': 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 LINE. {start_time}")
        try:
            # Get Social ID
            pack_social_user_pseudo_ids = []
            no_line_id = []
            user_pseudo_ids_success = []
            for uid in context.data['user_pseudo_ids']:
                social_id = self._get_line_uid(uid)
                if not social_id:
                    no_line_id.append(uid)
                user_pseudo_ids_success.append(uid)
                pack_social_user_pseudo_ids.append(social_id)
            
            channels_context = self._manage_line_uid(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 LINE channel {channel}")
                    
                    print(f"channels_context[channel]: {channels_context[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_line_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_line_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

            elif self.config['type'] =='audience':
                pass
            
                #Manage social ID by channel

        
        except Exception as e:
            print(f"Traceback for unexpected error in LINEAPI node: {e}")
            return False