"""WhatsApp handler for Lambda-based AI agents.

This module provides a complete WhatsApp handler that manages all the boilerplate:
- Webhook verification
- Signature verification
- Event parsing
- Error handling
- Response formatting
- Message deduplication (DynamoDB-based for Lambda horizontal scaling)
- Status updates (read receipts, reactions)

Usage:
    from agent_factory.handlers import WhatsAppAgentHandler
    from agent_factory.channels import WhatsAppChannel
    from agent_factory.core import AgentOrchestrator

    handler = WhatsAppAgentHandler(
        orchestrator=my_orchestrator,
        channel=WhatsAppChannel(
            access_token="EAAG...",
            phone_number_id="123456789",
            verify_token="my-verify-token",
            app_secret="abc123",
        ),
        dedup_table_name="my-dedup-table",  # Optional: DynamoDB table for deduplication
    )

    # In Lambda
    def lambda_handler(event, context):
        return handler.handle(event, context)
"""

import json
import os
import time
from typing import Any, Callable, Dict, Optional, Set

from agent_factory.channels.whatsapp import WhatsAppChannel
from agent_factory.core.orchestrator import AgentOrchestrator
from agent_factory.observability.logging import get_logger, log_handler_complete, log_handler_start
from agent_factory.observability.metrics import get_metrics, record_invocation, record_latency

logger = get_logger("whatsapp_handler")

# In-memory cache as fallback (works for warm Lambda instances)
_processed_messages: Set[str] = set()
_MAX_CACHE_SIZE = 1000

# DynamoDB client (lazy initialized)
_dynamodb_client = None


def _get_dynamodb_client():
    """Get or create DynamoDB client."""
    global _dynamodb_client
    if _dynamodb_client is None:
        import boto3

        _dynamodb_client = boto3.client("dynamodb")
    return _dynamodb_client


def _try_claim_message(table_name: str, dedup_key: str, ttl_seconds: int = 300) -> bool:
    """Try to claim a message for processing using DynamoDB conditional write.

    Returns True if this is the first Lambda to claim the message, False if already claimed.
    Uses conditional write to ensure atomicity across concurrent Lambda instances.
    """
    try:
        client = _get_dynamodb_client()
        ttl = int(time.time()) + ttl_seconds

        client.put_item(
            TableName=table_name,
            Item={
                "pk": {"S": dedup_key},
                "ttl": {"N": str(ttl)},
                "claimed_at": {"N": str(int(time.time() * 1000))},
            },
            ConditionExpression="attribute_not_exists(pk)",
        )
        return True  # Successfully claimed
    except Exception as e:
        if "ConditionalCheckFailedException" in str(type(e).__name__):
            return False  # Already claimed by another Lambda
        # Log but don't fail on other errors - fall back to in-memory
        logger.warning(f"DynamoDB dedup error: {e}")
        return True  # Allow processing on error (fail open)


class WhatsAppAgentHandler:
    """Complete WhatsApp handler for AI agents.

    Handles all WhatsApp integration boilerplate so agents only need to
    define their tools and system prompt.

    Example:
        from agent_factory.handlers import WhatsAppAgentHandler
        from agent_factory.channels import WhatsAppChannel
        from agent_factory.models import BedrockModel
        from agent_factory.core import AgentOrchestrator

        # Create orchestrator with your tools
        orchestrator = AgentOrchestrator(
            model=BedrockModel(),
            system_prompt="You are a helpful assistant...",
            tools=[MyTool()],
        )

        # Create handler
        handler = WhatsAppAgentHandler(
            orchestrator=orchestrator,
            channel=WhatsAppChannel(),
        )

        # Lambda entry point
        def lambda_handler(event, context):
            return handler.handle(event, context)
    """

    def __init__(
        self,
        orchestrator: AgentOrchestrator,
        channel: Optional[WhatsAppChannel] = None,
        *,
        on_error: Optional[Callable[[Exception], str]] = None,
        thinking_emoji: str = "\u23f3",  # Hourglass
        error_emoji: str = "\u274c",  # X mark
        dedup_table_name: Optional[str] = None,
        mark_as_read: bool = True,
    ):
        """Initialize the WhatsApp handler.

        Args:
            orchestrator: Agent orchestrator to process messages
            channel: WhatsApp channel (creates default if not provided)
            on_error: Custom error handler that returns user-facing message
            thinking_emoji: Emoji reaction while processing
            error_emoji: Emoji reaction on error
            dedup_table_name: DynamoDB table for message deduplication (recommended for Lambda).
                              Can also be set via WHATSAPP_DEDUP_TABLE env var.
                              Table must have 'pk' as partition key and optional 'ttl' for TTL.
            mark_as_read: Whether to send read receipts (default: True)
        """
        self.orchestrator = orchestrator
        self.channel = channel or WhatsAppChannel()
        self.on_error = on_error
        self.thinking_emoji = thinking_emoji
        self.error_emoji = error_emoji
        self.dedup_table_name = dedup_table_name or os.environ.get("WHATSAPP_DEDUP_TABLE")
        self.mark_as_read = mark_as_read
        self.metrics = get_metrics()

        # Add channel to orchestrator if not already added
        if self.channel.channel_type.value not in orchestrator._channels:
            orchestrator.add_channel(self.channel)

    def handle(self, event: Dict[str, Any], context: Any = None) -> Dict[str, Any]:
        """Handle a Lambda event from API Gateway.

        This method handles:
        - Webhook verification (GET requests)
        - Signature verification
        - Event parsing and routing
        - Error handling and response formatting

        Args:
            event: Lambda event (API Gateway format)
            context: Lambda context (optional)

        Returns:
            Lambda response dict with statusCode and body
        """
        start_time = time.time()
        log_handler_start(logger, "whatsapp_handler", event)

        try:
            # Check if this is a verification request (GET with query params)
            http_method = event.get("httpMethod", event.get("requestContext", {}).get("http", {}).get("method", ""))

            if http_method == "GET" and self.channel.is_verification_request(event):
                logger.info("Handling WhatsApp webhook verification")
                return self.channel.get_verification_response(event)

            # Parse body from API Gateway
            body = self._parse_body(event)
            headers = self._get_headers(event)

            # Verify signature
            body_bytes = self._get_raw_body(event)
            if not self.channel.verify_request(headers, body_bytes):
                logger.warning("Invalid WhatsApp signature")
                return self._response(401, {"error": "Invalid signature"})

            # Process the webhook event
            result = self._process_event(body)

            duration_ms = (time.time() - start_time) * 1000
            log_handler_complete(logger, "whatsapp_handler", status_code=200, duration_ms=duration_ms)
            record_invocation(self.metrics, "whatsapp_handler", success=True)
            record_latency(self.metrics, "whatsapp_handler", duration_ms)

            return result

        except Exception as e:
            duration_ms = (time.time() - start_time) * 1000
            logger.exception("Handler error")
            record_invocation(self.metrics, "whatsapp_handler", success=False)
            record_latency(self.metrics, "whatsapp_handler", duration_ms)

            # WhatsApp expects 200 OK even on errors (to prevent retries)
            return self._response(200, {"status": "error"})

    def _parse_body(self, event: Dict[str, Any]) -> Dict[str, Any]:
        """Parse body from various event formats."""
        body = event.get("body", event)

        if isinstance(body, str):
            try:
                return json.loads(body)
            except json.JSONDecodeError:
                return {}

        return body if isinstance(body, dict) else {}

    def _get_headers(self, event: Dict[str, Any]) -> Dict[str, str]:
        """Get headers with case-insensitive access."""
        headers = event.get("headers", {}) or {}
        return headers

    def _get_raw_body(self, event: Dict[str, Any]) -> bytes:
        """Get raw body bytes for signature verification."""
        body = event.get("body", "")
        if isinstance(body, bytes):
            return body
        return body.encode("utf-8") if body else b""

    def _process_event(self, body: Dict[str, Any]) -> Dict[str, Any]:
        """Process a WhatsApp webhook event."""
        global _processed_messages

        # Parse the message
        message = self.channel.parse_event(body)

        if not message:
            # Not a message event (could be status update, etc.)
            logger.debug("No message parsed from event")
            return self._response(200, {"status": "ok"})

        # Message deduplication using WhatsApp message ID
        message_id = message.metadata.get("message_id", "")
        if message_id:
            dedup_key = f"wa:{message_id}"

            # Try DynamoDB-based deduplication first
            if self.dedup_table_name:
                if not _try_claim_message(self.dedup_table_name, dedup_key):
                    logger.info(f"Skipping duplicate message (DynamoDB): {message_id}")
                    return self._response(200, {"status": "duplicate"})
            else:
                # Fall back to in-memory deduplication
                if dedup_key in _processed_messages:
                    logger.info(f"Skipping duplicate message (memory): {message_id}")
                    return self._response(200, {"status": "duplicate"})

                # Add to cache with size limit
                _processed_messages.add(dedup_key)
                if len(_processed_messages) > _MAX_CACHE_SIZE:
                    _processed_messages.clear()

        return self._handle_message(message)

    def _handle_message(self, message) -> Dict[str, Any]:
        """Handle a WhatsApp message."""
        phone_number = message.channel_id
        text = message.text
        message_id = message.metadata.get("message_id", "")

        logger.info(
            "Processing WhatsApp message",
            extra={
                "phone_number": phone_number,
                "contact_name": message.metadata.get("contact_name"),
                "message_id": message_id,
            },
        )

        # Mark as read
        if self.mark_as_read and message_id:
            self.channel.mark_as_read(message_id)

        # Add thinking reaction
        if message_id:
            self.channel.send_reaction(message_id, self.thinking_emoji, phone_number)

        try:
            # Build session ID from phone number
            session_id = f"whatsapp:{phone_number}"

            # Build context
            context = {
                "channel_type": "whatsapp",
                "channel_id": phone_number,
                "user_id": phone_number,
                "phone_number": phone_number,
                "contact_name": message.metadata.get("contact_name"),
                "message_id": message_id,
            }

            # Process message
            result = self.orchestrator.invoke(
                prompt=text,
                session_id=session_id,
                context=context,
            )

            # Send response
            response_text = result.get("result", "I'm sorry, I couldn't process that.")
            self.channel.send_message(
                self.channel.format_response(
                    text=response_text,
                    channel_id=phone_number,
                )
            )

            return self._response(200, {"status": "ok"})

        except Exception as e:
            logger.exception("Error processing message")

            # Add error reaction
            if message_id:
                self.channel.send_reaction(message_id, self.error_emoji, phone_number)

            # Send error message
            error_message = "I encountered an error processing your request."
            if self.on_error:
                error_message = self.on_error(e)

            self.channel.send_message(
                self.channel.format_response(
                    text=error_message,
                    channel_id=phone_number,
                )
            )

            return self._response(200, {"status": "error"})

    def _response(
        self,
        status_code: int,
        body: Dict[str, Any],
        headers: Optional[Dict[str, str]] = None,
    ) -> Dict[str, Any]:
        """Create a Lambda response."""
        default_headers = {
            "Content-Type": "application/json",
        }
        if headers:
            default_headers.update(headers)

        return {
            "statusCode": status_code,
            "headers": default_headers,
            "body": json.dumps(body),
        }


def create_whatsapp_handler(
    orchestrator: AgentOrchestrator,
    access_token: Optional[str] = None,
    phone_number_id: Optional[str] = None,
    verify_token: Optional[str] = None,
    app_secret: Optional[str] = None,
) -> Callable[[Dict[str, Any], Any], Dict[str, Any]]:
    """Factory function to create a WhatsApp Lambda handler.

    This is a convenience function that creates the handler and returns
    the handle method directly.

    Args:
        orchestrator: Agent orchestrator
        access_token: WhatsApp access token (or use env var)
        phone_number_id: WhatsApp phone number ID (or use env var)
        verify_token: Webhook verification token (or use env var)
        app_secret: App secret for signature verification (or use env var)

    Returns:
        Lambda handler function

    Example:
        from agent_factory.handlers import create_whatsapp_handler

        handler = create_whatsapp_handler(my_orchestrator)

        # This can be used directly as the Lambda handler
        # handler(event, context)
    """
    channel = WhatsAppChannel(
        access_token=access_token,
        phone_number_id=phone_number_id,
        verify_token=verify_token,
        app_secret=app_secret,
    )

    whatsapp_handler = WhatsAppAgentHandler(
        orchestrator=orchestrator,
        channel=channel,
    )

    return whatsapp_handler.handle
