#!/usr/bin/env python3
"""
Genesis GHL Mastery Skill
==========================
Supreme control module for GoHighLevel (GHL) integration.
Provides programmatic access to Locations, Leads, Workflows, and Funnel Assets.

PM-035: GHL Skill Enhancement
- Supports: create_contact, send_sms, trigger_workflow
- Handles rate limits with exponential backoff
"""

import os
import json
import time
import logging
import requests
from typing import Dict, Any, List, Optional
from pathlib import Path
from datetime import datetime

logger = logging.getLogger("GHLMasterySkill")
logging.basicConfig(level=logging.INFO)


class RateLimitError(Exception):
    """Raised when API rate limit is exceeded."""
    pass


class GHLMasterySkill:
    """
    Expertise in GHL API (LeadConnector) operations.
    Complete API integration with rate limit handling.
    """

    def __init__(self, api_key: Optional[str] = None, location_id: Optional[str] = None):
        self.api_key = api_key or self._load_key()
        self.location_id = location_id or os.getenv("GHL_LOCATION_ID")
        self.base_url = "https://services.leadconnectorhq.com"
        self.headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
            "Version": "2021-07-28"
        }
        # Rate limit tracking
        self.rate_limit_remaining = 100
        self.rate_limit_reset = 0
        self.max_retries = 3
        self.retry_delay = 1.0

        logger.info(f"GHL Mastery Skill Initialized (API Key: {'Configured' if self.api_key else 'MISSING'})")

    def _load_key(self) -> Optional[str]:
        """Attempts to load the GHL Agency/Location key from the system."""
        # Check environment variable first
        env_key = os.getenv("GHL_API_KEY")
        if env_key:
            return env_key

        key_files = [
            r"E:\genesis-system\GHL-AGILEADAPT-MASTER-SUBACCOUNT-API-KEY",
            "/mnt/e/genesis-system/GHL-AGILEADAPT-MASTER-SUBACCOUNT-API-KEY",
        ]
        for kf in key_files:
            if os.path.exists(kf):
                with open(kf, 'r') as f:
                    return f.read().strip()
        return None

    def _handle_rate_limit(self, response: requests.Response) -> None:
        """Update rate limit tracking from response headers."""
        self.rate_limit_remaining = int(response.headers.get('X-RateLimit-Remaining', 100))
        self.rate_limit_reset = int(response.headers.get('X-RateLimit-Reset', 0))

        if response.status_code == 429:
            wait_time = max(self.rate_limit_reset - time.time(), 5)
            logger.warning(f"Rate limited. Waiting {wait_time:.1f}s")
            raise RateLimitError(f"Rate limited. Retry after {wait_time}s")

    def _make_request(self, method: str, endpoint: str, data: Dict = None,
                      params: Dict = None) -> Dict[str, Any]:
        """Make API request with rate limit handling and exponential backoff."""
        url = f"{self.base_url}{endpoint}"

        for attempt in range(self.max_retries):
            try:
                if method.upper() == "GET":
                    response = requests.get(url, headers=self.headers, params=params, timeout=30)
                elif method.upper() == "POST":
                    response = requests.post(url, headers=self.headers, json=data, timeout=30)
                elif method.upper() == "PUT":
                    response = requests.put(url, headers=self.headers, json=data, timeout=30)
                elif method.upper() == "DELETE":
                    response = requests.delete(url, headers=self.headers, timeout=30)
                else:
                    raise ValueError(f"Unsupported method: {method}")

                self._handle_rate_limit(response)

                if response.status_code in [200, 201]:
                    return response.json() if response.text else {"status": "success"}
                elif response.status_code == 404:
                    return {"error": "not_found", "status_code": 404}
                else:
                    logger.error(f"API Error {response.status_code}: {response.text}")
                    return {"error": response.text, "status_code": response.status_code}

            except RateLimitError:
                wait_time = self.retry_delay * (2 ** attempt)
                logger.info(f"Rate limit retry {attempt + 1}/{self.max_retries} in {wait_time}s")
                time.sleep(wait_time)
            except requests.exceptions.Timeout:
                logger.error(f"Request timeout on attempt {attempt + 1}")
                if attempt == self.max_retries - 1:
                    return {"error": "timeout", "status_code": 408}
                time.sleep(self.retry_delay)
            except Exception as e:
                logger.error(f"Request failed: {e}")
                return {"error": str(e)}

        return {"error": "max_retries_exceeded"}

    # ===== CONTACT OPERATIONS =====

    def create_contact(self, location_id: str, first_name: str, last_name: str,
                       email: str, phone: str, tags: List[str] = None,
                       custom_fields: Dict[str, Any] = None) -> Dict[str, Any]:
        """
        Creates a contact in a GHL sub-account.

        Args:
            location_id: GHL location/sub-account ID
            first_name: Contact's first name
            last_name: Contact's last name
            email: Contact's email address
            phone: Contact's phone number
            tags: Optional list of tags to apply
            custom_fields: Optional custom field values

        Returns:
            Dict with contact ID and status
        """
        logger.info(f"Creating contact: {first_name} {last_name} ({email})")

        payload = {
            "firstName": first_name,
            "lastName": last_name,
            "email": email,
            "phone": phone,
            "locationId": location_id
        }

        if tags:
            payload["tags"] = tags
        if custom_fields:
            payload["customFields"] = custom_fields

        result = self._make_request("POST", "/contacts/", data=payload)

        if "contact" in result:
            logger.info(f"Contact created: {result['contact'].get('id')}")

        return result

    def get_contact(self, contact_id: str) -> Dict[str, Any]:
        """Retrieves a contact by ID."""
        return self._make_request("GET", f"/contacts/{contact_id}")

    def update_contact(self, contact_id: str, updates: Dict[str, Any]) -> Dict[str, Any]:
        """Updates an existing contact."""
        logger.info(f"Updating contact: {contact_id}")
        return self._make_request("PUT", f"/contacts/{contact_id}", data=updates)

    def search_contacts(self, location_id: str, query: str = None,
                        email: str = None, phone: str = None) -> List[Dict[str, Any]]:
        """Search contacts by query, email, or phone."""
        params = {"locationId": location_id}
        if query:
            params["query"] = query
        if email:
            params["email"] = email
        if phone:
            params["phone"] = phone

        result = self._make_request("GET", "/contacts/", params=params)
        return result.get("contacts", [])

    # ===== SMS OPERATIONS =====

    def send_sms(self, contact_id: str, message: str,
                 location_id: str = None) -> Dict[str, Any]:
        """
        Sends an SMS message to a contact.

        Args:
            contact_id: Target contact ID
            message: SMS message content
            location_id: Optional location override

        Returns:
            Dict with message status
        """
        loc_id = location_id or self.location_id
        logger.info(f"Sending SMS to contact {contact_id}")

        payload = {
            "type": "SMS",
            "contactId": contact_id,
            "message": message
        }

        if loc_id:
            payload["locationId"] = loc_id

        return self._make_request("POST", "/conversations/messages", data=payload)

    def send_bulk_sms(self, contact_ids: List[str], message: str,
                      location_id: str = None) -> List[Dict[str, Any]]:
        """Send SMS to multiple contacts with rate limiting."""
        results = []
        for contact_id in contact_ids:
            result = self.send_sms(contact_id, message, location_id)
            results.append({"contact_id": contact_id, **result})
            # Respect rate limits
            if self.rate_limit_remaining < 10:
                time.sleep(1)
        return results

    # ===== WORKFLOW OPERATIONS =====

    def trigger_workflow(self, location_id: str, workflow_id: str,
                         contact_id: str, event_data: Dict = None) -> Dict[str, Any]:
        """
        Triggers a GHL workflow for a specific contact.

        Args:
            location_id: GHL location ID
            workflow_id: Target workflow ID
            contact_id: Contact to add to workflow
            event_data: Optional additional event data

        Returns:
            Dict with trigger status
        """
        logger.info(f"Triggering workflow {workflow_id} for contact {contact_id}")

        payload = {
            "contactId": contact_id,
            "locationId": location_id,
            "eventData": event_data or {}
        }

        return self._make_request("POST", f"/workflows/{workflow_id}/trigger", data=payload)

    def list_workflows(self, location_id: str) -> List[Dict[str, Any]]:
        """Lists all workflows for a location."""
        result = self._make_request("GET", f"/workflows/?locationId={location_id}")
        return result.get("workflows", [])

    def get_workflow(self, workflow_id: str, location_id: str) -> Dict[str, Any]:
        """Gets workflow details."""
        params = {"locationId": location_id}
        return self._make_request("GET", f"/workflows/{workflow_id}", params=params)

    # ===== LOCATION OPERATIONS =====

    def get_location_details(self, location_id: str) -> Dict[str, Any]:
        """Fetches complete data for a specific GHL location."""
        logger.info(f"Fetching details for Location: {location_id}")
        return self._make_request("GET", f"/locations/{location_id}")

    def list_locations(self) -> List[Dict[str, Any]]:
        """Lists all accessible locations."""
        result = self._make_request("GET", "/locations/")
        return result.get("locations", [])

    # ===== FUNNEL OPERATIONS =====

    def upload_funnel_asset(self, location_id: str, file_path: str) -> Dict[str, Any]:
        """Uploads an image or document to the GHL Media Library."""
        logger.info(f"Uploading {file_path} to Location {location_id} media library")

        if not os.path.exists(file_path):
            return {"error": "file_not_found", "path": file_path}

        # For file uploads, we need multipart form data
        url = f"{self.base_url}/medias/upload"
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Version": "2021-07-28"
        }

        try:
            with open(file_path, 'rb') as f:
                files = {'file': (os.path.basename(file_path), f)}
                data = {'locationId': location_id}
                response = requests.post(url, headers=headers, files=files, data=data, timeout=60)

            if response.status_code in [200, 201]:
                return response.json()
            return {"error": response.text, "status_code": response.status_code}
        except Exception as e:
            logger.error(f"Upload failed: {e}")
            return {"error": str(e)}

    def list_funnels(self, location_id: str) -> List[Dict[str, Any]]:
        """Lists all funnels for a location."""
        result = self._make_request("GET", f"/funnels/?locationId={location_id}")
        return result.get("funnels", [])

    # ===== OPPORTUNITY OPERATIONS =====

    def create_opportunity(self, location_id: str, contact_id: str,
                          pipeline_id: str, stage_id: str,
                          name: str, value: float = 0) -> Dict[str, Any]:
        """Creates an opportunity in a pipeline."""
        logger.info(f"Creating opportunity: {name} for contact {contact_id}")

        payload = {
            "locationId": location_id,
            "contactId": contact_id,
            "pipelineId": pipeline_id,
            "pipelineStageId": stage_id,
            "name": name,
            "monetaryValue": value
        }

        return self._make_request("POST", "/opportunities/", data=payload)

    def update_opportunity_stage(self, opportunity_id: str,
                                 stage_id: str) -> Dict[str, Any]:
        """Moves an opportunity to a different stage."""
        return self._make_request("PUT", f"/opportunities/{opportunity_id}",
                                  data={"pipelineStageId": stage_id})

    # ===== UTILITY METHODS =====

    def execute(self, action: str, **kwargs) -> Dict[str, Any]:
        """
        Generic action executor for skill interface compatibility.

        Args:
            action: Action name (create_contact, send_sms, trigger_workflow, etc.)
            **kwargs: Action-specific parameters

        Returns:
            Action result
        """
        action_map = {
            "create_contact": self.create_contact,
            "get_contact": self.get_contact,
            "update_contact": self.update_contact,
            "search_contacts": self.search_contacts,
            "send_sms": self.send_sms,
            "send_bulk_sms": self.send_bulk_sms,
            "trigger_workflow": self.trigger_workflow,
            "list_workflows": self.list_workflows,
            "get_location": self.get_location_details,
            "list_locations": self.list_locations,
            "upload_asset": self.upload_funnel_asset,
            "list_funnels": self.list_funnels,
            "create_opportunity": self.create_opportunity,
            "update_opportunity": self.update_opportunity_stage
        }

        if action not in action_map:
            return {"error": f"Unknown action: {action}", "available": list(action_map.keys())}

        try:
            return action_map[action](**kwargs)
        except TypeError as e:
            return {"error": f"Invalid parameters for {action}: {e}"}
        except Exception as e:
            return {"error": str(e)}

    def get_rate_limit_status(self) -> Dict[str, Any]:
        """Returns current rate limit status."""
        return {
            "remaining": self.rate_limit_remaining,
            "reset_at": self.rate_limit_reset,
            "reset_in": max(0, self.rate_limit_reset - time.time())
        }


if __name__ == "__main__":
    # Self-test
    ghl = GHLMasterySkill()
    if ghl.api_key:
        # Test contact creation
        result = ghl.execute("create_contact",
                            location_id="mNao44FybCdLJGxvK3YT",
                            first_name="Test",
                            last_name="Contact",
                            email="test@example.com",
                            phone="+61400000000",
                            tags=["genesis", "test"])
        print(f"[TEST] Create Contact: {result}")

        # Test workflow trigger
        result = ghl.execute("trigger_workflow",
                            location_id="mNao44FybCdLJGxvK3YT",
                            workflow_id="wf_test_123",
                            contact_id="con_test_123")
        print(f"[TEST] Trigger Workflow: {result}")

        # Rate limit status
        print(f"[TEST] Rate Limit: {ghl.get_rate_limit_status()}")
    else:
        print("[TEST] Skipping API calls (Key missing)")
