import requests
import json
import urllib.parse
import re
import uuid
import base64
from typing import List, Dict, Optional, Tuple, Union, Any
import os

server_url = os.getenv('superU_SERVER_URL', 'https://voip-middlware.superu.ai')
# server_url = 'http://localhost:5000'

class CallWrapper:
    def __init__(self, api_key):
        self.api_key = api_key

    def create_twilio_call(self, phoneNumberId, to_, assistant_id , additional_payload = None):
        """Create a Twilio call request for an assistant.
        
        Args:
            phoneNumberId: Twilio phone number ID configured in SuperU
            to_: Destination phone number to call
            assistant_id: Assistant ID that should handle the call
            additional_payload: Optional metadata passed with the call request
            
        Returns:
            Dict containing call creation response from the API
        """
        request_body = {
            "api_key" : self.api_key,
            "phoneNumberId" : phoneNumberId,
            "to_" : to_,
            "assistant_id" : assistant_id,
            "additional_payload" : additional_payload
        }
        
        # print("twilio call request body" , request_body)
        response = requests.post(f'{server_url}/pypi_support/twilio_call_create', json=request_body, headers={"superU-Api-Key": self.api_key})
        # print(response.json())
        return response.json()
    
    def analysis_twilio_call(self, call_uuid , custom_fields = None):
        """Analyze a completed Twilio call with optional custom fields.
        
        Args:
            call_uuid: UUID of the call to analyze
            custom_fields: Optional list of field definitions with keys:
                - field: Field name
                - definition: Extraction instructions
                - outputs_options: Allowed output values
            
        Returns:
            Dict containing call analysis output
            
        Raises:
            ValueError: If custom_fields structure is invalid
        """
        if custom_fields is not None:
            required_keys = {"field", "definition", "outputs_options"}
            for i, field in enumerate(custom_fields):
                if not isinstance(field, dict):
                    raise ValueError(f"custom_fields[{i}] is not a dictionary")
                
                missing_keys = required_keys - field.keys()
                if missing_keys:
                    raise ValueError(f"custom_fields[{i}] is missing keys: {missing_keys}")

                if not isinstance(field["field"], str):
                    raise ValueError(f"custom_fields[{i}]['field'] must be a string")
                if not isinstance(field["definition"], str):
                    raise ValueError(f"custom_fields[{i}]['definition'] must be a string")
                if not isinstance(field["outputs_options"], list) or not all(isinstance(opt, str) for opt in field["outputs_options"]):
                    raise ValueError(f"custom_fields[{i}]['outputs_options'] must be a list of strings")

        response = requests.request(
            "POST",
            f'{server_url}/pypi_support/twilio_call_analysis',
            json={'api_key': self.api_key, "call_uuid": call_uuid, "custom_fields": custom_fields},
            headers={"superU-Api-Key": self.api_key}
        )
        return response.json()
    
    def create_outbound_call(self, assistant_id, to : str, from_ : str = None,campaign_id : str = "demo_call", customer_name : str = 'Unknown', customer_id : str = 'Unknown', variable_values : dict = None , ):
        """Create an outbound campaign call.
        
        Args:
            assistant_id: Assistant ID used to place the call
            to: Destination phone number
            from_: Optional source phone number
            campaign_id: Campaign identifier for grouping calls
            customer_name: Customer display name
            customer_id: Customer identifier
            variable_values: Optional template variables for the call flow
            
        Returns:
            Dict containing created call details
            
        Raises:
            ValueError: If assistant_id is missing
            Exception: If API request fails
        """
        if not assistant_id :
            raise ValueError("assistant_id is required")
        
        payload = {
            'api_key': self.api_key,
            'assistant_id': assistant_id,
            'campaign_id': campaign_id,
            'to': to,
            'customer_name': customer_name,
            'customer_id': customer_id
        }
        
        if from_:
            payload['from'] = from_
        if variable_values:
            payload['variable_values'] = variable_values
        
        response = requests.post(f'{server_url}/campaign/outbound/create_call/superu', json=payload, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to create outbound call: {response.status_code}, {response.text}")
        return response.json()


    # def __getattr__(self, name):
    #     # Delegate all other methods/attributes
    #     return getattr(self._real, name)

class AssistantWrapper:
    def __init__(self, api_key):
        self.api_key = api_key
    
    def create_version(self, agent_id, version, assistant_data, knowledge_base=None, tools=None, call_forwarding=None):
        """Create a new version for an existing agent.
        
        Args:
            agent_id: Agent ID to version
            version: Version name or label
            assistant_data: Assistant configuration payload
            knowledge_base: Optional knowledge base IDs to attach
            tools: Optional tool IDs to attach
            call_forwarding: Optional call forwarding configuration
            
        Returns:
            Dict containing version creation response
            
        Raises:
            ValueError: If required fields are missing
            Exception: If API request fails
        """
        if not agent_id or not version or not assistant_data:
            raise ValueError("agent_id, version, and assistant_data are required")
        
        payload = {
            'api_key': self.api_key,
            'agent_id': agent_id,
            'version': version,
            'assistant_data': assistant_data
        }
        
        if knowledge_base is not None:
            payload['knowledgeBase'] = knowledge_base
        if tools is not None:
            payload['tools'] = tools
        if call_forwarding is not None:
            payload['call_forwarding'] = call_forwarding
        
        response = requests.post(f'{server_url}/agent/version/create', json=payload, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to create agent version: {response.status_code}, {response.text}")
        return response.json()
    
    def update_version(self, agent_id, version_id, version=None, assistant_data=None, composio_app=None):
        """Update an existing agent version.
        
        Args:
            agent_id: Agent ID owning the version
            version_id: Version ID to update
            version: Optional new version label
            assistant_data: Optional assistant configuration payload
            composio_app: Optional composio app configuration
            
        Returns:
            Dict containing updated version details
            
        Raises:
            ValueError: If required IDs are missing or nothing to update
            Exception: If API request fails
        """
        if not version_id or not agent_id:
            raise ValueError("version_id and agent_id are required")
        
        if version is None and assistant_data is None and composio_app is None:
            raise ValueError("At least one of version, assistant_data, or composio_app must be provided")
        
        payload = {
            'api_key': self.api_key,
            'agent_id': agent_id,
            'version_id': version_id
        }
        
        if version is not None:
            payload['version'] = version
        if assistant_data is not None:
            payload['assistant_data'] = assistant_data
        if composio_app is not None:
            payload['composio-app'] = composio_app
        
        response = requests.post(f'{server_url}/agent/version/update', json=payload, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to update agent version: {response.status_code}, {response.text}")
        return response.json()
    
    def list(self, page=1, limit=20, inbound_or_outbound=None, search_query=None):
        """List agents with optional filtering.
        
        Args:
            page: Page number for pagination
            limit: Number of items per page
            inbound_or_outbound: Optional call direction filter
            search_query: Optional text query for filtering agents
            
        Returns:
            Dict containing paginated agent results
            
        Raises:
            Exception: If API request fails
        """
        params = {
            'page': page,
            'limit': limit
        }
        if inbound_or_outbound:
            params['inbound_or_outbound'] = inbound_or_outbound
        
        payload = {'api_key': self.api_key}
        if search_query:
            payload['search_query'] = search_query
        
        response = requests.post(f'{server_url}/agent/list', json=payload, params=params , headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to list agents: {response.status_code}, {response.text}")
        return response.json()
    
    def get_version(self, agent_id, version):
        """Get a specific agent version by version label.
        
        Args:
            agent_id: Agent ID to query
            version: Version label to retrieve
            
        Returns:
            Dict containing version details
            
        Raises:
            ValueError: If required parameters are missing
            Exception: If API request fails
        """
        if not agent_id or not version:
            raise ValueError("agent_id and version are required")
        
        payload = {
            'api_key': self.api_key,
            'agent_id': agent_id,
            'version': version
        }
        
        response = requests.post(f'{server_url}/agent/version/get', json=payload, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to get agent version: {response.status_code}, {response.text}")
        return response.json()
    
    def list_versions(self, agent_id):
        """List all versions for an agent.
        
        Args:
            agent_id: Agent ID to query
            
        Returns:
            Dict containing version list
            
        Raises:
            ValueError: If agent_id is missing
            Exception: If API request fails
        """
        if not agent_id:
            raise ValueError("agent_id is required")
        
        payload = {
            'api_key': self.api_key,
            'agent_id': agent_id
        }
        
        response = requests.post(f'{server_url}/agent/version/list', json=payload, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to list agent versions: {response.status_code}, {response.text}")
        return response.json()
    
    def deploy_version(self, agent_id, version_id):
        """Deploy a specific version for an agent.
        
        Args:
            agent_id: Agent ID owning the version
            version_id: Version ID to deploy
            
        Returns:
            Dict containing deployment result
            
        Raises:
            ValueError: If required IDs are missing
            Exception: If API request fails
        """
        if not agent_id or not version_id:
            raise ValueError("agent_id and version_id are required")
        
        payload = {
            'api_key': self.api_key,
            'agent_id': agent_id,
            'version_id': version_id
        }
        
        response = requests.post(f'{server_url}/agent/version/deploy', json=payload, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to deploy agent version: {response.status_code}, {response.text}")
        return response.json()
    
    def create_agent(self, type=None, name=None, company_name=None, assistant_name=None, first_message=None, 
                    voice_id=None, voice_provider='11labs', speed='1.0', bg_noice=False, system_prompt=None, 
                    industry='Blank Template', useCase='Blank Template', form_model=None, assistant_data=None, 
                    knowledge_base : [str] = None, tools : [str] = None, call_forwarding : [str] = None):
        """Create a new SuperU agent.
        
        Args:
            type: Optional assistant type
            name: Optional internal agent name
            company_name: Optional company name
            assistant_name: Optional assistant display name
            first_message: Optional initial greeting
            voice_id: Optional voice model ID
            voice_provider: Voice provider name
            speed: Voice speed factor
            bg_noice: Whether background noise simulation is enabled
            system_prompt: Optional system prompt/script for the assistant
            industry: Industry template label
            useCase: Use case template label
            form_model: Optional form model config
            assistant_data: Optional complete assistant configuration payload
            knowledge_base: Optional knowledge base IDs
            tools: Optional tool IDs
            call_forwarding: Optional call forwarding configuration
            
        Returns:
            Dict containing created agent details
            
        Raises:
            Exception: If API request fails
        """
        payload = {
            'api_key': self.api_key
        }
        
        if type is not None:
            payload['type'] = type
        
        if name:
            payload['name'] = name
        if company_name:
            payload['company_name'] = company_name
        if assistant_name:
            payload['assistant_name'] = assistant_name
        if first_message:
            payload['first_message'] = first_message
        if voice_id:
            payload['voice'] = voice_id
            payload['voice_id'] = voice_id
        if voice_provider:
            payload['voice_provider'] = voice_provider
        if speed:
            payload['speed'] = speed
        if bg_noice is not None:
            payload['bg_noice'] = str(bg_noice).lower()
            payload['backgroundNoise'] = bg_noice
        if system_prompt:
            payload['script'] = system_prompt
        payload['industry'] = industry or 'Blank Template'
        if useCase:
            payload['useCase'] = useCase
        if form_model:
            payload['form_model'] = form_model
        if assistant_data:
            payload['assistant_data'] = assistant_data
        if knowledge_base:
            payload['knowledgeBase'] = knowledge_base
        if tools:
            payload['tools'] = tools
        if call_forwarding:
            payload['call_forwarding'] = call_forwarding
        
        response = requests.post(f'{server_url}/agent/create', json=payload, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to create agent: {response.status_code}, {response.text}")
        return response.json()
    
    def update_name(self, agent_id, name):
        """Update an agent's display name.
        
        Args:
            agent_id: Agent ID to update
            name: New name value
            
        Returns:
            Dict containing update status
            
        Raises:
            ValueError: If required fields are missing
            Exception: If API request fails
        """
        if not agent_id or not name:
            raise ValueError("agent_id and name are required")
        
        payload = {
            'api_key': self.api_key,
            'agent_id': agent_id,
            'name': name
        }
        
        response = requests.post(f'{server_url}/agent/update/name', json=payload, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to update agent name: {response.status_code}, {response.text}")
        return response.json()
    
    def import_agents(self):
        """Import agents for the authenticated account.
        
        Returns:
            Dict containing import result
            
        Raises:
            Exception: If API request fails
        """
        response = requests.get(f'{server_url}/agent/import/{self.api_key}' , headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to import agents: {response.status_code}, {response.text}")
        return response.json()
    
    def delete(self, agent_id):
        """Delete an agent.
        
        Args:
            agent_id: Agent ID to delete
            
        Returns:
            Dict containing deletion status
            
        Raises:
            ValueError: If agent_id is missing
            Exception: If API request fails
        """
        if not agent_id:
            raise ValueError("agent_id is required")
        
        payload = {
            'api_key': self.api_key,
            'agent_object_id': agent_id
        }
        
        response = requests.post(f'{server_url}/agent/delete', json=payload, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to delete agent: {response.status_code}, {response.text}")
        return response.json()

class PhoneNumberWrapper:
    def __init__(self, api_key):
        self.api_key = api_key
    
    def get_owned(self):
        """Fetch phone numbers owned by the authenticated user.
        
        Returns:
            Dict containing owned phone numbers
            
        Raises:
            Exception: If API request fails
        """
        payload = {
            'api_key': self.api_key
        }
        
        response = requests.post(f'{server_url}/phoneNumber/owned', json=payload, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to get owned phone numbers: {response.status_code}, {response.text}")
        return response.json()

class CallLogsWrapper:
    def __init__(self, api_key):
        self.api_key = api_key
    
    def get_logs(self, assistant_id='all', limit=20, page=1, before=None, after=None, 
                 status=None, campaign_id=None, search_query=None):
        """Retrieve call logs with optional filtering and pagination.
        
        Args:
            assistant_id: Assistant ID or 'all' for global logs
            limit: Number of records per page
            page: Page number for pagination
            before: Optional upper timestamp/date bound
            after: Optional lower timestamp/date bound
            status: Optional call status filter
            campaign_id: Optional campaign filter
            search_query: Optional free-text filter
            
        Returns:
            Dict containing call log records
            
        Raises:
            ValueError: If assistant_id is missing
            Exception: If API request fails
        """
        if not assistant_id:
            raise ValueError("assistant_id is required")
        
        params = {
            'limit': limit,
            'page': page
        }
        
        if before:
            params['before'] = before
        if after:
            params['after'] = after
        if status:
            params['status'] = status
        if campaign_id:
            params['campaign_id'] = campaign_id
        if search_query:
            params['search_query'] = search_query
        
        payload = {
            'api_key': self.api_key
        }
        
        response = requests.post(f'{server_url}/call-logs/{assistant_id}', json=payload, params=params, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to get call logs: {response.status_code}, {response.text}")
        return response.json()

class ToolsWrapper:
    def __init__(self, api_key):
        self.api_key = api_key
    
    def create(self, tool_data):
        """Create a new tool.
        
        Args:
            tool_data: Tool configuration payload
            
        Returns:
            Dict containing created tool details
            
        Raises:
            ValueError: If tool_data is invalid
            Exception: If API request fails
        """
        if not tool_data or not isinstance(tool_data, dict):
            raise ValueError("tool_data must be a non-empty dictionary")
        
        payload = {
            'api_key': self.api_key,
            **tool_data
        }
        
        response = requests.post(f'{server_url}/tool', json=payload, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 201:
            raise Exception(f"Failed to create tool: {response.status_code}, {response.text}")
        return response.json()
    
    def list(self, page=1, per_page=20, tool_type=None):
        """List tools with optional type filtering.
        
        Args:
            page: Page number for pagination
            per_page: Number of records per page
            tool_type: Optional tool type filter
            
        Returns:
            Dict containing paginated tool results
            
        Raises:
            Exception: If API request fails
        """
        payload = {
            'api_key': self.api_key,
            'page': page,
            'per_page': per_page
        }
        
        if tool_type:
            payload['type'] = tool_type
        
        response = requests.post(f'{server_url}/tool/list', json=payload, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to list tools: {response.status_code}, {response.text}")
        return response.json()
    
    def get(self, tool_id):
        """Get a single tool by ID.
        
        Args:
            tool_id: Tool ID to retrieve
            
        Returns:
            Dict containing tool details
            
        Raises:
            ValueError: If tool_id is missing
            Exception: If API request fails
        """
        if not tool_id:
            raise ValueError("tool_id is required")
        
        params = {
            'user_id': self.api_key
        }
        
        response = requests.get(f'{server_url}/tool/{tool_id}', params=params, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to get tool: {response.status_code}, {response.text}")
        return response.json()
    
    def update(self, tool_id, update_data):
        """Update an existing tool.
        
        Args:
            tool_id: Tool ID to update
            update_data: Partial update payload
            
        Returns:
            Dict containing updated tool details
            
        Raises:
            ValueError: If input is invalid
            Exception: If API request fails
        """
        if not tool_id:
            raise ValueError("tool_id is required")
        if not update_data or not isinstance(update_data, dict):
            raise ValueError("update_data must be a non-empty dictionary")
        
        payload = {
            'api_key': self.api_key,
            **update_data
        }
        
        response = requests.patch(f'{server_url}/tool/{tool_id}', json=payload, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to update tool: {response.status_code}, {response.text}")
        return response.json()
    
    def delete(self, tool_id):
        """Delete a tool.
        
        Args:
            tool_id: Tool ID to delete
            
        Returns:
            Dict containing deletion status
            
        Raises:
            ValueError: If tool_id is missing
            Exception: If API request fails
        """
        if not tool_id:
            raise ValueError("tool_id is required")
        
        params = {
            'user_id': self.api_key
        }
        
        response = requests.delete(f'{server_url}/tool/{tool_id}', params=params, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to delete tool: {response.status_code}, {response.text}")
        return response.json()

class FolderWrapper:
    def __init__(self, api_key):
        self.api_key = api_key
    
    def create(self, folder_name, description=None):
        """Create a new folder.
        
        Args:
            folder_name: Name of the folder
            description: Optional folder description
            
        Returns:
            Dict containing created folder details
            
        Raises:
            ValueError: If folder_name is missing
            Exception: If API request fails
        """
        if not folder_name:
            raise ValueError("folder_name is required")
        
        payload = {
            'api_key': self.api_key,
            'folder_name': folder_name
        }
        
        if description is not None:
            payload['description'] = description
        
        response = requests.post(f'{server_url}/folder/create', json=payload, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to create folder: {response.status_code}, {response.text}")
        return response.json()
    
    def list(self, page=1, per_page=20):
        """List folders for the authenticated user.
        
        Args:
            page: Page number for pagination
            per_page: Number of folders per page
            
        Returns:
            Dict containing paginated folder results
            
        Raises:
            Exception: If API request fails
        """
        payload = {
            'api_key': self.api_key,
            'page': page,
            'per_page': per_page
        }
        
        response = requests.post(f'{server_url}/folder/list', json=payload, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to list folders: {response.status_code}, {response.text}")
        return response.json()
    
    def get(self, folder_id):
        """Get a single folder by ID.
        
        Args:
            folder_id: Folder ID to retrieve
            
        Returns:
            Dict containing folder details
            
        Raises:
            ValueError: If folder_id is missing
            Exception: If API request fails
        """
        if not folder_id:
            raise ValueError("folder_id is required")
        
        payload = {
            'api_key': self.api_key
        }
        
        response = requests.post(f'{server_url}/folder/{folder_id}', json=payload, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to get folder details: {response.status_code}, {response.text}")
        return response.json()
    
    def update(self, folder_id, folder_name=None, description=None):
        """Update folder metadata.
        
        Args:
            folder_id: Folder ID to update
            folder_name: Optional new folder name
            description: Optional new description
            
        Returns:
            Dict containing updated folder details
            
        Raises:
            ValueError: If input is invalid
            Exception: If API request fails
        """
        if not folder_id:
            raise ValueError("folder_id is required")
        
        if folder_name is None and description is None:
            raise ValueError("At least one of folder_name or description must be provided")
        
        payload = {
            'api_key': self.api_key,
            'folder_id': folder_id
        }
        
        if folder_name is not None:
            payload['folder_name'] = folder_name
        if description is not None:
            payload['description'] = description
        
        response = requests.post(f'{server_url}/folder/update', json=payload, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to update folder: {response.status_code}, {response.text}")
        return response.json()
    
    def delete(self, folder_id):
        """Delete a folder.
        
        Args:
            folder_id: Folder ID to delete
            
        Returns:
            Dict containing deletion status
            
        Raises:
            ValueError: If folder_id is missing
            Exception: If API request fails
        """
        if not folder_id:
            raise ValueError("folder_id is required")
        
        payload = {
            'api_key': self.api_key,
            'folder_id': folder_id
        }
        
        response = requests.post(f'{server_url}/folder/delete', json=payload, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to delete folder: {response.status_code}, {response.text}")
        return response.json()
    
    def assign_agent(self, agent_id, folder_id=None):
        """Assign an agent to a folder.
        
        Args:
            agent_id: Agent ID to assign
            folder_id: Optional folder ID. If omitted, behavior depends on backend defaults.
            
        Returns:
            Dict containing assignment result
            
        Raises:
            ValueError: If agent_id is missing
            Exception: If API request fails
        """
        if not agent_id:
            raise ValueError("agent_id is required")
        
        payload = {
            'api_key': self.api_key,
            'agent_id': agent_id
        }
        
        if folder_id is not None:
            payload['folder_id'] = folder_id
        
        response = requests.post(f'{server_url}/folder/assign-agent', json=payload, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to assign agent to folder: {response.status_code}, {response.text}")
        return response.json()
    
    def get_agents(self, folder_id, page=1, per_page=20):
        """List agents assigned to a folder.
        
        Args:
            folder_id: Folder ID to query
            page: Page number for pagination
            per_page: Number of records per page
            
        Returns:
            Dict containing paginated agent results
            
        Raises:
            ValueError: If folder_id is missing
            Exception: If API request fails
        """
        if not folder_id:
            raise ValueError("folder_id is required")
        
        payload = {
            'api_key': self.api_key,
            'page': page,
            'per_page': per_page
        }
        
        response = requests.post(f'{server_url}/folder/{folder_id}/agents', json=payload, headers={"superU-Api-Key": self.api_key})
        if response.status_code != 200:
            raise Exception(f"Failed to get agents by folder: {response.status_code}, {response.text}")
        return response.json()
  
class ContactWrapper:
    """Wrapper for Contact management API endpoints.
    
    Provides methods for creating and listing individual contacts.
    """
    
    def __init__(self, api_key: str):
        self.api_key = api_key
        self._phone_pattern = re.compile(r'^[+]?[0-9\s\-\(\)]+$')
        self._country_code_pattern = re.compile(r'^[+]?[0-9]{1,4}$')
        self._email_pattern = re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')

    def _validate_contact_data(self, contact: dict) -> tuple[bool, list[str]]:
        errors = []
        required_fields = ['first_name', 'last_name', 'email', 'country_code', 'phone_number']
        
        # Check required fields
        for field in required_fields:
            if not contact.get(field) or str(contact.get(field)).strip() == '':
                errors.append(f"Missing or empty '{field}'")
        
        if errors:
            return False, errors
        
        # Validate first and last name
        first_name = contact['first_name'].strip()
        last_name = contact['last_name'].strip()
        if not first_name or not last_name:
            errors.append("First name and last name cannot be empty")
        
        # Validate email
        email = contact['email'].strip().lower()
        if not self._email_pattern.match(email):
            errors.append("Invalid email format")
        
        # Validate phone number
        phone_number = contact['phone_number'].strip()
        if not self._phone_pattern.match(phone_number):
            errors.append("Invalid phone number format")
        
        # Validate country code
        country_code = contact['country_code'].strip()
        if not self._country_code_pattern.match(country_code):
            errors.append("Invalid country code format")
        
        return len(errors) == 0, errors

    def create(self, first_name: str, last_name: str, email: str, 
               country_code: str, phone_number: str, audience_id: str = None) -> dict:
        """Create a new contact.
        
        Args:
            first_name: Contact's first name
            last_name: Contact's last name
            email: Contact's email address
            country_code: Phone country code (e.g., "+1")
            phone_number: Contact's phone number
            audience_id: Optional audience ID to associate contact with
            
        Returns:
            Dict containing success status, message, and contact_id
            
        Raises:
            ValueError: If validation fails
            Exception: If API request fails
        """
        contact_data = {
            'first_name': first_name,
            'last_name': last_name,
            'email': email,
            'country_code': country_code,
            'phone_number': phone_number
        }
        
        if audience_id:
            contact_data['audience_id'] = audience_id
        
        # Validate contact data
        is_valid, errors = self._validate_contact_data(contact_data)
        if not is_valid:
            raise ValueError(f"Contact validation failed: {'; '.join(errors)}")
        
        # Prepare payload with normalized data
        payload = {
            'first_name': first_name.strip(),
            'last_name': last_name.strip(),
            'email': email.strip().lower(),
            'country_code': country_code.strip(),
            'phone_number': phone_number.strip()
        }
        
        if audience_id:
            payload['audience_id'] = audience_id
        
        response = requests.post(
            f'{server_url}/contact/create',
            json=payload,
            headers={"superU-Api-Key": self.api_key}
        )
        
        if response.status_code not in [200, 201]:
            raise Exception(f"Failed to create contact: {response.status_code}, {response.text}")
        
        return response.json()

    def list(self, page: int = 1, limit: int = 10, search_query: str = None) -> dict:
        """List all contacts for the current user with pagination and search.
        
        Args:
            page: Page number (starting from 1)
            limit: Number of contacts per page (1-100)
            search_query: Optional search query to filter contacts by name, email, or phone
            
        Returns:
            Dict containing contact_list and pagination metadata
            
        Raises:
            ValueError: If parameters are invalid
            Exception: If API request fails
        """
        # Validate parameters
        if page < 1:
            raise ValueError("Page must be greater than 0")
        if limit < 1 or limit > 100:
            raise ValueError("Limit must be between 1 and 100")
        
        params = {
            'page': page,
            'limit': limit
        }
        
        if search_query:
            params['search_query'] = search_query
        
        response = requests.get(
            f'{server_url}/contact/list',
            params=params,
            headers={"superU-Api-Key": self.api_key, "Content-Type": "application/json"}
        )
        
        if response.status_code != 200:
            raise Exception(f"Failed to list contacts: {response.status_code}, {response.text}")
        
        return response.json()
    
class AudienceWrapper:
    """Wrapper for Audience management API endpoints.
    
    Provides methods for creating, listing, updating, and deleting audiences,
    as well as managing contacts within audiences.
    """
    
    def __init__(self, api_key: str):
        self.api_key = api_key
        self._phone_pattern = re.compile(r'^\d{7,15}$')
        self._country_code_pattern = re.compile(r'^\+\d{1,4}$')
        self._email_pattern = re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')

    def _validate_phone_number(self, phone_number: str) -> tuple[bool, str | None]:
        cleaned = re.sub(r'[\s\-\(\)\+]+', '', str(phone_number).strip())
        
        # Handle float-like strings (e.g., "9970000000.0")
        if '.' in cleaned:
            parts = cleaned.split('.')
            if len(parts) == 2 and (parts[1] == '0' or parts[1] == ''):
                cleaned = parts[0]
            else:
                return False, "Phone number contains invalid decimal characters"
        
        if not cleaned.isdigit():
            return False, "Phone number must contain only digits"
        
        if len(cleaned) < 7 or len(cleaned) > 15:
            return False, f"Phone number must have 7-15 digits (got {len(cleaned)})"
        
        return True, None

    def _validate_contact(self, contact: dict, index: int = 0) -> tuple[bool, list[str]]:
        errors = []
        required_fields = ['first_name', 'country_code', 'phone_number']
        
        # Check required fields
        for field in required_fields:
            if not contact.get(field) or str(contact.get(field)).strip() == '':
                errors.append(f"Missing or empty '{field}'")
        
        if errors:
            return False, errors
        
        # Validate phone number
        phone_number = str(contact['phone_number']).strip()
        is_valid, error_msg = self._validate_phone_number(phone_number)
        if not is_valid:
            errors.append(error_msg)
        
        # Validate country code
        country_code = str(contact['country_code']).strip()
        if not self._country_code_pattern.match(country_code):
            errors.append("Invalid country code format (expected: +1 to +9999)")
        
        # Validate email if provided
        email = contact.get('email', '').strip()
        if email and not self._email_pattern.match(email):
            errors.append("Invalid email format")
        
        return len(errors) == 0, errors

    def _validate_contacts(self, contacts: list[dict]) -> tuple[bool, list[str]]:
        all_errors = []
        for i, contact in enumerate(contacts):
            is_valid, errors = self._validate_contact(contact, i)
            if not is_valid:
                all_errors.append(f"Contact {i+1}: {', '.join(errors)}")
        
        return len(all_errors) == 0, all_errors

    def create(self, audience_name: str, contacts: list[dict], audience_description: str = "") -> dict:
        """Create a new audience with contacts.
        
        Args:
            audience_name: Name for the audience
            contacts: List of contact dictionaries, each containing:
                - first_name (required): Contact's first name
                - country_code (required): Phone country code (e.g., "+1")
                - phone_number (required): Phone number (7-15 digits)
                - last_name (optional): Contact's last name
                - email (optional): Contact's email address
                - variable_values (optional): Dict of custom variables
            audience_description: Optional description for the audience
            
        Returns:
            Dict containing audience_id, contacts_added, and contact details
            
        Raises:
            ValueError: If validation fails for any contact
            Exception: If API request fails
        """
        if not audience_name or not audience_name.strip():
            raise ValueError("audience_name is required and cannot be empty")
        
        if not contacts or len(contacts) == 0:
            raise ValueError("At least one contact is required")
        
        # Validate all contacts
        is_valid, errors = self._validate_contacts(contacts)
        if not is_valid:
            raise ValueError(f"Contact validation failed: {'; '.join(errors)}")
        
        payload = {
            "api_key": self.api_key,
            "audience_name": audience_name.strip(),
            "audience_description": audience_description.strip() if audience_description else "",
            "contacts": contacts
        }
        
        response = requests.post(f'{server_url}/audience/create', json=payload, headers={"superU-Api-Key": self.api_key})
        
        if response.status_code != 200:
            raise Exception(f"Failed to create audience: {response.status_code}, {response.text}")
        
        return response.json()

    def list(self) -> dict:
        """List all audiences for the current user.
        
        Returns:
            Dict containing audience_list with id, name, description, contactCount, createdAt
            
        Raises:
            Exception: If API request fails
        """
        params = {"page": 1, "limit": 10}
        
        response = requests.get(
            f'{server_url}/audience/list',
            params=params,
            headers={"superU-Api-Key": self.api_key, "Content-Type": "application/json"}
        )
        
        if response.status_code != 200:
            raise Exception(f"Failed to list audiences: {response.status_code}, {response.text}")
        
        return response.json()

    def get(self, audience_id: str) -> dict:
        """Get detailed information about a specific audience.
        
        Args:
            audience_id: UUID of the audience to retrieve
            
        Returns:
            Dict containing audience details and all contacts
            
        Raises:
            ValueError: If audience_id is not provided
            Exception: If API request fails
        """
        if not audience_id:
            raise ValueError("audience_id is required")
        
        response = requests.get(
            f'{server_url}/audience/{audience_id}',
            headers={"superU-Api-Key": self.api_key}
        )
        
        if response.status_code == 404:
            raise Exception(f"Audience not found: {audience_id}")
        
        if response.status_code != 200:
            raise Exception(f"Failed to get audience: {response.status_code}, {response.text}")
        
        return response.json()

    def get_contacts(self, audience_id: str, page: int = 1, limit: int = 10) -> dict:
        """Get paginated contacts for a specific audience.
        
        Args:
            audience_id: UUID of the audience
            page: Page number (starting from 1)
            limit: Number of contacts per page
            
        Returns:
            Dict containing contact_list and pagination metadata
            
        Raises:
            ValueError: If audience_id is not provided
            Exception: If API request fails
        """
        if not audience_id:
            raise ValueError("audience_id is required")
        
        params = {
            "audience_id": audience_id,
            "page": max(1, page),
            "limit": max(1, limit)
        }
        
        response = requests.get(
            f'{server_url}/audience/contacts/list',
            params=params,
            headers={"superU-Api-Key": self.api_key}
        )
        
        if response.status_code != 200:
            raise Exception(f"Failed to get contacts: {response.status_code}, {response.text}")
        
        return response.json()

    def update(self, audience_id: str, audience_name: str = None, audience_description: str = None) -> dict:
        """Update audience name and/or description.
        
        Args:
            audience_id: UUID of the audience to update
            audience_name: New name for the audience (optional)
            audience_description: New description for the audience (optional)
            
        Returns:
            Dict containing updated audience details
            
        Raises:
            ValueError: If audience_id is not provided or no fields to update
            Exception: If API request fails
        """
        if not audience_id:
            raise ValueError("audience_id is required")
        
        if audience_name is None and audience_description is None:
            raise ValueError("At least one of audience_name or audience_description must be provided")
        
        payload = {
            "api_key": self.api_key,
            "audience_id": audience_id
        }
        
        if audience_name is not None:
            if not audience_name.strip():
                raise ValueError("audience_name cannot be empty")
            payload["audience_name"] = audience_name.strip()
        
        if audience_description is not None:
            payload["audience_description"] = audience_description.strip()
        
        response = requests.put(f'{server_url}/audience/update', json=payload, headers={"superU-Api-Key": self.api_key})
        
        if response.status_code == 404:
            raise Exception(f"Audience not found: {audience_id}")
        
        if response.status_code != 200:
            raise Exception(f"Failed to update audience: {response.status_code}, {response.text}")
        
        return response.json()

    def add_contacts(self, audience_id: str, contacts: List[Dict]) -> dict:
        """Add one or more contacts to an existing audience.
        
        Args:
            audience_id: UUID of the audience
            contacts: List of contact dictionaries (same format as create())
            
        Returns:
            Dict containing contacts_added count and inserted contact details
            
        Raises:
            ValueError: If validation fails
            Exception: If API request fails
        """
        if not audience_id:
            raise ValueError("audience_id is required")
        
        if not contacts or len(contacts) == 0:
            raise ValueError("At least one contact is required")
        
        # Validate all contacts
        is_valid, errors = self._validate_contacts(contacts)
        if not is_valid:
            raise ValueError(f"Contact validation failed: {'; '.join(errors)}")
        
        payload = {
            "api_key": self.api_key,
            "audience_id": audience_id,
            "contacts": contacts
        }
        
        response = requests.post(f'{server_url}/audience/add-contacts', json=payload, headers={"superU-Api-Key": self.api_key})
        
        if response.status_code == 404:
            raise Exception(f"Audience not found: {audience_id}")
        
        if response.status_code != 200:
            raise Exception(f"Failed to add contacts: {response.status_code}, {response.text}")
        
        return response.json()

    def delete(self, audience_id: str) -> dict:
        """Delete an audience and all its contacts.
        
        Args:
            audience_id: UUID of the audience to delete
            
        Returns:
            Dict containing deletion confirmation and contacts_deleted count
            
        Raises:
            ValueError: If audience_id is not provided
            Exception: If API request fails
        """
        if not audience_id:
            raise ValueError("audience_id is required")
        
        payload = {
            "api_key": self.api_key,
            "audience_id": audience_id
        }
        
        response = requests.delete(f'{server_url}/audience/delete', json=payload, headers={"superU-Api-Key": self.api_key})
        
        if response.status_code == 404:
            raise Exception(f"Audience not found: {audience_id}")
        
        if response.status_code != 200:
            raise Exception(f"Failed to delete audience: {response.status_code}, {response.text}")
        
        return response.json()
    
class KnowledgeBaseWrapper:
    """Wrapper for Knowledge Base management API endpoints.
    
    Provides methods for creating, listing, and retrieving knowledge bases with file uploads.
    """
    
    def __init__(self, api_key: str):
        self.api_key = api_key

    def create(self, name: str, description: str, files: list) -> dict:
        """Create a new knowledge base with files.
        
        Args:
            name: Knowledge base name
            description: Knowledge base description , Only allowed are csv and PDF
            files: List of file objects or tuples (filename, file_content, content_type)
                   Examples:
                   - [open('file.pdf', 'rb')]
                   - [('file.pdf', pdf_bytes, 'application/pdf')]
            
        Returns:
            Dict containing knowledge_base_id, kb_uuid, embedding_status, and file details
            
        Raises:
            ValueError: If validation fails
            Exception: If API request fails
        """
        if not name or not name.strip():
            raise ValueError("name is required and cannot be empty")
        
        if not description or not description.strip():
            raise ValueError("description is required and cannot be empty")
        
        if not files or len(files) == 0:
            raise ValueError("At least one file is required")
        
        # Prepare multipart form data
        form_data = {
            'name': name.strip(),
            'description': description.strip()
        }
        
        # Prepare files for upload
        # The 'files' parameter expects a list of tuples: (field_name, (filename, file_object, content_type))
        files_data = []
        for file_item in files:
            if hasattr(file_item, 'read'):
                # File-like object
                filename = getattr(file_item, 'name', 'uploaded_file')
                files_data.append(('files', (filename, file_item, 'application/octet-stream')))
            elif isinstance(file_item, tuple) and len(file_item) >= 2:
                # Tuple format: (filename, content) or (filename, content, content_type)
                filename = file_item[0]
                content = file_item[1]
                content_type = file_item[2] if len(file_item) > 2 else 'application/octet-stream'
                files_data.append(('files', (filename, content, content_type)))
            else:
                raise ValueError(f"Invalid file format. Expected file object or tuple, got {type(file_item)}")
        
        response = requests.post(
            f'{server_url}/knowledge-base/create',
            data=form_data,
            files=files_data,
            headers={"superU-Api-Key": self.api_key}
        )
        
        if response.status_code != 200:
            raise Exception(f"Failed to create knowledge base: {response.status_code}, {response.text}")
        
        result = response.json()
        if result.get('status') != 'success':
            raise Exception(f"Failed to create knowledge base: {result.get('message', 'Unknown error')}")
        
        return result.get('data', result)

    def list(self, page: int = 1, limit: int = 10) -> dict:
        """List all knowledge bases for the current user with pagination.
        
        Args:
            page: Page number (starting from 1)
            limit: Number of knowledge bases per page (1-50)
            
        Returns:
            Dict containing knowledge_bases list and pagination metadata
            
        Raises:
            ValueError: If parameters are invalid
            Exception: If API request fails
        """
        # Validate parameters
        if page < 1:
            raise ValueError("Page must be greater than 0")
        if limit < 1 or limit > 50:
            raise ValueError("Limit must be between 1 and 50")
        
        payload = {
            'page': page,
            'limit': limit
        }
        
        response = requests.post(
            f'{server_url}/knowledge-base/list',
            json=payload,
            headers={"superU-Api-Key": self.api_key}
        )
        
        if response.status_code != 200:
            raise Exception(f"Failed to list knowledge bases: {response.status_code}, {response.text}")
        
        result = response.json()
        if result.get('status') != 'success':
            raise Exception(f"Failed to list knowledge bases: {result.get('message', 'Unknown error')}")
        
        return result.get('data', result)

    def get(self, knowledge_base_id: str) -> dict:
        """Get detailed information about a specific knowledge base.
        
        Args:
            knowledge_base_id: Knowledge base ID (UUID or MongoDB ObjectId)
            
        Returns:
            Dict containing complete knowledge base details including files
            
        Raises:
            ValueError: If knowledge_base_id is not provided
            Exception: If API request fails or knowledge base not found
        """
        if not knowledge_base_id:
            raise ValueError("knowledge_base_id is required")
        
        response = requests.get(
            f'{server_url}/knowledge-base/{knowledge_base_id}',
            headers={"superU-Api-Key": self.api_key}
        )
        
        if response.status_code == 404:
            raise Exception(f"Knowledge base not found: {knowledge_base_id}")
        
        if response.status_code != 200:
            raise Exception(f"Failed to get knowledge base: {response.status_code}, {response.text}")
        
        result = response.json()
        if result.get('status') != 'success':
            raise Exception(f"Failed to get knowledge base: {result.get('message', 'Unknown error')}")
        
        return result.get('data', result)

class SuperU:
    def __init__(self, api_key):
        API_key_validation = self.validate_api_key(api_key)
        self.api_key = api_key
        self.user_id = API_key_validation['user_id']
        self.calls = CallWrapper(self.api_key)
        self.agents = AssistantWrapper(self.api_key)
        self.folders = FolderWrapper(self.api_key)
        self.call_logs = CallLogsWrapper(self.api_key)
        self.tools = ToolsWrapper(self.api_key)
        self.phone_numbers = PhoneNumberWrapper(self.api_key)
        self.contacts = ContactWrapper(self.api_key)
        self.audience = AudienceWrapper(self.api_key)
        self.knowledge_base = KnowledgeBaseWrapper(self.api_key)


    def validate_api_key(self, api_key):
        response = requests.post(
            # 'https://shared-service.superu.ai/api_key_check',
            f'{server_url}/user/validate-api-key',
            headers={'Content-Type': 'application/json'},
            json={'api_key': api_key}
        )
        if response.status_code != 200:
            raise Exception(f"Invalid API key: {response.status_code}, {response.text}")
        return response.json()
    
    # def __getattr__(self, name):
    #     return getattr(self._client, name)
