"""
Webhook service for external integrations.
"""
from sqlalchemy.orm import Session
from typing import List, Dict, Any, Optional
from datetime import datetime, timedelta
import secrets
import httpx
import hmac
import hashlib
import json
from ..models.webhook import Webhook, WebhookDelivery, WebhookEventType, WebhookStatus, WebhookDeliveryStatus
from ...shared.config.logging_config import get_logger
from ...analytics.services.audit_service import audit_service

logger = get_logger(__name__)

class WebhookService:
    """Service for managing webhooks."""
    
    @staticmethod
    def create_webhook(
        db: Session,
        name: str,
        url: str,
        events: List[str],
        created_by: int,
        description: Optional[str] = None,
        retry_count: int = 3,
        timeout_seconds: int = 30
    ) -> Webhook:
        """Create a new webhook."""
        secret = secrets.token_urlsafe(32)
        
        webhook = Webhook(
            name=name,
            url=url,
            secret=secret,
            events=events,
            created_by=created_by,
            description=description,
            retry_count=retry_count,
            timeout_seconds=timeout_seconds
        )
        
        db.add(webhook)
        db.commit()
        db.refresh(webhook)
        
        logger.info(f'Webhook created: {webhook.id} - {name}')
        return webhook
    
    @staticmethod
    def update_webhook(
        db: Session,
        webhook_id: int,
        name: Optional[str] = None,
        url: Optional[str] = None,
        events: Optional[List[str]] = None,
        description: Optional[str] = None,
        status: Optional[WebhookStatus] = None,
        retry_count: Optional[int] = None,
        timeout_seconds: Optional[int] = None
    ) -> Optional[Webhook]:
        """Update a webhook."""
        webhook = db.query(Webhook).filter(Webhook.id == webhook_id).first()
        if not webhook:
            return None
        
        if name is not None:
            webhook.name = name
        if url is not None:
            webhook.url = url
        if events is not None:
            webhook.events = events
        if description is not None:
            webhook.description = description
        if status is not None:
            webhook.status = status
        if retry_count is not None:
            webhook.retry_count = retry_count
        if timeout_seconds is not None:
            webhook.timeout_seconds = timeout_seconds
        
        db.commit()
        db.refresh(webhook)
        
        logger.info(f'Webhook updated: {webhook.id} - {webhook.name}')
        return webhook
    
    @staticmethod
    def delete_webhook(db: Session, webhook_id: int) -> bool:
        """Delete a webhook."""
        webhook = db.query(Webhook).filter(Webhook.id == webhook_id).first()
        if not webhook:
            return False
        
        db.delete(webhook)
        db.commit()
        
        logger.info(f'Webhook deleted: {webhook_id}')
        return True
    
    @staticmethod
    def generate_signature(payload: str, secret: str) -> str:
        """Generate HMAC signature for webhook payload."""
        return hmac.new(
            secret.encode('utf-8'),
            payload.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()
    
    @staticmethod
    async def deliver_webhook(
        db: Session,
        event_type: str,
        event_id: str,
        payload: Dict[str, Any]
    ) -> List[WebhookDelivery]:
        """Deliver webhook event to all subscribed webhooks."""
        # Find active webhooks subscribed to this event
        webhooks = db.query(Webhook).filter(
            Webhook.status == WebhookStatus.active,
            Webhook.events.contains([event_type])
        ).all()
        
        deliveries = []
        
        for webhook in webhooks:
            delivery = WebhookDelivery(
                webhook_id=webhook.id,
                event_type=event_type,
                event_id=event_id,
                payload=payload,
                status=WebhookDeliveryStatus.pending
            )
            db.add(delivery)
            db.commit()
            db.refresh(delivery)
            
            # Deliver asynchronously
            try:
                await WebhookService._send_webhook(webhook, delivery, payload)
            except Exception as e:
                logger.error(f'Error delivering webhook {delivery.id}: {str(e)}')
            
            deliveries.append(delivery)
        
        return deliveries
    
    @staticmethod
    async def _send_webhook(
        webhook: Webhook,
        delivery: WebhookDelivery,
        payload: Dict[str, Any]
    ):
        """Send webhook HTTP request."""
        payload_str = json.dumps(payload)
        signature = WebhookService.generate_signature(payload_str, webhook.secret)
        
        headers = {
            'Content-Type': 'application/json',
            'X-Webhook-Signature': signature,
            'X-Webhook-Event': delivery.event_type,
            'X-Webhook-Id': str(delivery.id)
        }
        
        try:
            async with httpx.AsyncClient(timeout=webhook.timeout_seconds) as client:
                response = await client.post(
                    webhook.url,
                    content=payload_str,
                    headers=headers
                )
                
                delivery.response_status = response.status_code
                delivery.response_body = response.text[:1000]  # Limit response body size
                delivery.attempt_count += 1
                
                if response.status_code >= 200 and response.status_code < 300:
                    delivery.status = WebhookDeliveryStatus.success
                    delivery.delivered_at = datetime.utcnow()
                else:
                    delivery.status = WebhookDeliveryStatus.failed
                    delivery.error_message = f'HTTP {response.status_code}'
                    
                    # Schedule retry if attempts remaining
                    if delivery.attempt_count < webhook.retry_count:
                        delivery.status = WebhookDeliveryStatus.retrying
                        delivery.next_retry_at = datetime.utcnow() + timedelta(
                            minutes=2 ** delivery.attempt_count  # Exponential backoff
                        )
        except Exception as e:
            delivery.status = WebhookDeliveryStatus.failed
            delivery.error_message = str(e)[:500]
            delivery.attempt_count += 1
            
            # Schedule retry if attempts remaining
            if delivery.attempt_count < webhook.retry_count:
                delivery.status = WebhookDeliveryStatus.retrying
                delivery.next_retry_at = datetime.utcnow() + timedelta(
                    minutes=2 ** delivery.attempt_count
                )
        
        # Update delivery in database
        from ...shared.config.database import SessionLocal
        db = SessionLocal()
        try:
            db.merge(delivery)
            db.commit()
        except Exception as e:
            logger.error(f'Error updating webhook delivery: {str(e)}')
            db.rollback()
        finally:
            db.close()

webhook_service = WebhookService()

