"""Process webhooks with signature verification, idempotency, and custom handlers."""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/14_utils_webhook.ipynb.

# %% auto 0
__all__ = ['logger', 'verify_webhook_signature', 'check_idempotency', 'log_webhook_event', 'update_webhook_status',
           'process_webhook', 'handle_webhook_request']

# %% ../nbs/14_utils_webhook.ipynb 2
from fastcore.utils import *
from fastcore.all import *
from fastsql import *
import hmac
import hashlib
import json
import os
import logging
from datetime import datetime
from typing import Callable, Dict, Any, Optional

# Module-level logger - configured by app via configure_logging()
logger = logging.getLogger(__name__)

# %% ../nbs/14_utils_webhook.ipynb 5
def verify_webhook_signature(
    payload: str,  # Raw request body as string
    signature: str,  # Signature from header (format: "sha256=<hex>")
    secret: Optional[str] = None  # Secret key, defaults to WEBHOOK_SECRET env var
) -> bool:
    """Verify HMAC-SHA256 signature for webhook payload. Returns True if valid."""
    secret = secret or os.getenv('WEBHOOK_SECRET')
    if not secret:
        raise ValueError("WEBHOOK_SECRET not configured")
    
    # Compute expected signature
    expected = hmac.new(
        secret.encode('utf-8'),
        payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    
    # Extract hex from signature (handle "sha256=<hex>" format)
    if '=' in signature:
        signature = signature.split('=', 1)[1]
    
    # Constant-time comparison to prevent timing attacks
    return hmac.compare_digest(expected, signature)

# %% ../nbs/14_utils_webhook.ipynb 9
def check_idempotency(
    db: Database,  # Tenant database connection
    idempotency_key: str  # Unique key for this webhook event
) -> bool:
    """Check if webhook event already processed. Returns True if duplicate."""
    result = db.q(
        "SELECT webhook_id FROM webhook_events WHERE idempotency_key = ?",
        [idempotency_key]
    )
    return len(result) > 0

# %% ../nbs/14_utils_webhook.ipynb 12
def log_webhook_event(
    db: Database,  # Tenant database connection
    webhook_id: str,  # Unique webhook ID
    source: str,  # Source system (e.g., "stripe", "github")
    event_type: str,  # Event type (e.g., "payment.success")
    payload: Dict[str, Any],  # Full webhook payload
    signature: str,  # Request signature
    idempotency_key: str,  # Idempotency key
    status: str = 'pending'  # Status: pending, processing, completed, failed
):
    """Log webhook event to database"""
    db.insert(dict(
        webhook_id=webhook_id,
        source=source,
        event_type=event_type,
        payload_json=json.dumps(payload),
        signature=signature,
        idempotency_key=idempotency_key,
        status=status,
        created_at=datetime.utcnow().isoformat()
    ), 'webhook_events')

# %% ../nbs/14_utils_webhook.ipynb 15
def update_webhook_status(
    db: Database,  # Tenant database connection
    webhook_id: str,  # Webhook ID to update
    status: str,  # New status
    error_message: Optional[str] = None  # Optional error message
):
    """Update webhook event status and processed timestamp"""
    update_data = {
        'status': status,
        'processed_at': datetime.utcnow().isoformat()
    }
    if error_message:
        update_data['error_message'] = error_message
    
    db.update(update_data, 'webhook_events', 'webhook_id', webhook_id)

# %% ../nbs/14_utils_webhook.ipynb 18
async def process_webhook(
    db: Database,  # Tenant database connection
    webhook_id: str,  # Unique webhook ID
    source: str,  # Source system
    event_type: str,  # Event type
    payload: Dict[str, Any],  # Webhook payload
    signature: str,  # Request signature
    idempotency_key: str,  # Idempotency key
    raw_body: str,  # Raw request body for signature verification
    handler: Callable,  # App-specific webhook handler function
    secret: Optional[str] = None  # Optional webhook secret
) -> Dict[str, Any]:
    """Process webhook with verification, idempotency, and custom handler execution"""
    
    # Verify signature
    if not verify_webhook_signature(raw_body, signature, secret):
        return {'status': 'error', 'message': 'Invalid signature'}
    
    # Check idempotency
    if check_idempotency(db, idempotency_key):
        return {'status': 'duplicate', 'message': 'Event already processed'}
    
    # Log event
    log_webhook_event(db, webhook_id, source, event_type, payload, signature, idempotency_key, 'processing')
    
    try:
        # Execute custom handler
        result = await handler(payload, db)
        
        # Update status
        update_webhook_status(db, webhook_id, 'completed')
        
        return {'status': 'success', 'result': result}
    
    except Exception as e:
        # Log failure
        logger.error(f"Webhook {webhook_id} ({source}/{event_type}) failed: {e}", exc_info=True)
        update_webhook_status(db, webhook_id, 'failed', str(e))
        raise

# %% ../nbs/14_utils_webhook.ipynb 20
@delegates(process_webhook)
async def handle_webhook_request(
    request,  # FastHTML request object
    db: Database,  # Tenant database instance
    source: str,  # Webhook source identifier
    handler: Callable,  # App-specific handler function
    signature_header: str = 'X-Webhook-Signature',  # Header containing signature
    idempotency_header: str = 'X-Idempotency-Key',  # Header containing idempotency key
    event_type_field: str = 'type',  # Field in payload containing event type
    **kwargs  # Additional args passed to process_webhook
) -> tuple:  # Returns (response_dict, status_code)
    """FastHTML route handler for webhook requests.
    
    Example:
        @app.post('/webhooks/stripe')
        async def stripe_webhook(request):
            return await handle_webhook_request(
                request=request,
                db=get_tenant_db(request),
                source='stripe',
                handler=handle_stripe_event,
                signature_header='X-Stripe-Signature',
                run_in_background=True
            )
    """
    
    try:
        # Parse request
        payload = await request.json()
        signature = request.headers.get(signature_header)
        idempotency_key = request.headers.get(idempotency_header) or payload.get('id')
        event_type = payload.get(event_type_field, 'unknown')
        
        # Validate required fields
        if not signature:
            return {'error': f'Missing {signature_header} header'}, 401
        if not idempotency_key:
            return {'error': f'Missing {idempotency_header} or id field'}, 400
        
        # Process webhook
        result = process_webhook(
            db=db,
            source=source,
            event_type=event_type,
            payload=payload,
            signature=signature,
            idempotency_key=idempotency_key,
            handler=handler,
            **kwargs
        )
        
        # Return appropriate response
        if result['status'] == 'duplicate':
            return {'status': 'ok', 'message': 'Event already processed'}, 200
        elif result['status'] == 'accepted':
            return {
                'status': 'accepted',
                'webhook_id': result['webhook_id'],
                'job_id': result['job_id']
            }, 202
        else:
            return {
                'status': 'ok',
                'webhook_id': result['webhook_id']
            }, 200
    
    except ValueError as e:
        # Signature verification failed
        logger.warning(f"Webhook signature verification failed from {source}: {e}")
        return {'error': str(e)}, 401
    except Exception as e:
        # Other errors
        logger.error(f"Webhook processing failed: {e}", exc_info=True)
        return {'error': f'Processing failed: {str(e)}'}, 500
