"""
Webhook Delivery System

This module handles the delivery of webhooks to external URLs.
Webhooks are signed with HMAC-SHA256 and include retry logic.
"""

import hashlib
import hmac
import json
import time
import uuid
import requests
from django.utils import timezone


def generate_signature(payload: str, secret: str, timestamp: int) -> str:
    """
    Generate HMAC-SHA256 signature for webhook payload.

    The signature is computed as: HMAC-SHA256(timestamp.payload, secret)

    Args:
        payload: JSON string of the webhook payload
        secret: The webhook subscription's secret key
        timestamp: Unix timestamp when the webhook was sent

    Returns:
        Hexadecimal signature string
    """
    message = f"{timestamp}.{payload}"
    signature = hmac.new(
        secret.encode('utf-8'),
        message.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    return signature


def verify_signature(payload: str, secret: str, timestamp: int, signature: str) -> bool:
    """
    Verify a webhook signature.

    Args:
        payload: JSON string of the webhook payload
        secret: The webhook subscription's secret key
        timestamp: Unix timestamp from the X-Webhook-Timestamp header
        signature: Signature from the X-Webhook-Signature header

    Returns:
        True if signature is valid, False otherwise
    """
    expected = generate_signature(payload, secret, timestamp)
    return hmac.compare_digest(expected, signature)


def create_webhook_payload(event_type: str, data: dict) -> dict:
    """
    Create a standardized webhook payload.

    Args:
        event_type: The event type (e.g., 'appointment.created')
        data: The event data to include

    Returns:
        Complete webhook payload dict
    """
    return {
        'id': f"evt_{uuid.uuid4().hex[:24]}",
        'type': event_type,
        'created_at': timezone.now().isoformat(),
        'data': data,
    }


def deliver_webhook(subscription, event_type: str, data: dict) -> bool:
    """
    Synchronously deliver a webhook to the subscription URL.

    This function creates a WebhookDelivery record, sends the webhook,
    and updates the delivery status based on the response.

    Args:
        subscription: WebhookSubscription instance
        event_type: The event type being delivered
        data: The event data

    Returns:
        True if delivery succeeded, False otherwise
    """
    from .models import WebhookDelivery

    # Create the payload
    payload = create_webhook_payload(event_type, data)
    payload_json = json.dumps(payload, separators=(',', ':'))

    # Generate timestamp and signature
    timestamp = int(time.time())
    signature = generate_signature(payload_json, subscription.secret, timestamp)

    # Create delivery record
    delivery = WebhookDelivery.objects.create(
        subscription=subscription,
        event_type=event_type,
        event_id=payload['id'],
        payload=payload,
    )

    # Prepare headers
    headers = {
        'Content-Type': 'application/json',
        'User-Agent': 'SmoothSchedule-Webhook/1.0',
        'X-Webhook-ID': payload['id'],
        'X-Webhook-Timestamp': str(timestamp),
        'X-Webhook-Signature': signature,
    }

    # Send the webhook
    try:
        response = requests.post(
            subscription.url,
            data=payload_json,
            headers=headers,
            timeout=30,  # 30 second timeout
        )

        if 200 <= response.status_code < 300:
            delivery.mark_success(response.status_code, response.text[:10240])
            return True
        else:
            delivery.mark_failure(
                f"HTTP {response.status_code}",
                status_code=response.status_code,
                response_body=response.text[:10240]
            )
            return False

    except requests.exceptions.Timeout:
        delivery.mark_failure("Request timed out after 30 seconds")
        return False
    except requests.exceptions.ConnectionError as e:
        delivery.mark_failure(f"Connection error: {str(e)[:200]}")
        return False
    except requests.exceptions.RequestException as e:
        delivery.mark_failure(f"Request failed: {str(e)[:200]}")
        return False
    except Exception as e:
        delivery.mark_failure(f"Unexpected error: {str(e)[:200]}")
        return False


def queue_webhook_delivery(subscription, event_type: str, data: dict):
    """
    Queue a webhook for delivery.

    In production, this should use Celery or another task queue.
    For now, we deliver synchronously but catch all errors to not
    block the main request.

    Args:
        subscription: WebhookSubscription instance
        event_type: The event type being delivered
        data: The event data
    """
    try:
        # TODO: In production, use Celery:
        # deliver_webhook_task.delay(subscription.id, event_type, data)

        # For now, deliver synchronously
        deliver_webhook(subscription, event_type, data)
    except Exception:
        # Never let webhook delivery failures affect the main flow
        pass


def retry_failed_webhooks():
    """
    Retry failed webhook deliveries that are due for retry.

    This function should be called periodically (e.g., every minute)
    by a scheduled task to retry failed deliveries.
    """
    from .models import WebhookDelivery

    now = timezone.now()

    # Find deliveries due for retry
    deliveries = WebhookDelivery.objects.filter(
        success=False,
        next_retry_at__lte=now,
        subscription__is_active=True,
    ).select_related('subscription')[:100]  # Process in batches

    for delivery in deliveries:
        try:
            # Re-deliver the webhook
            subscription = delivery.subscription
            payload_json = json.dumps(delivery.payload, separators=(',', ':'))

            timestamp = int(time.time())
            signature = generate_signature(payload_json, subscription.secret, timestamp)

            headers = {
                'Content-Type': 'application/json',
                'User-Agent': 'SmoothSchedule-Webhook/1.0',
                'X-Webhook-ID': delivery.event_id,
                'X-Webhook-Timestamp': str(timestamp),
                'X-Webhook-Signature': signature,
                'X-Webhook-Retry': str(delivery.retry_count),
            }

            response = requests.post(
                subscription.url,
                data=payload_json,
                headers=headers,
                timeout=30,
            )

            if 200 <= response.status_code < 300:
                delivery.mark_success(response.status_code, response.text[:10240])
            else:
                delivery.mark_failure(
                    f"HTTP {response.status_code}",
                    status_code=response.status_code,
                    response_body=response.text[:10240]
                )

        except requests.exceptions.Timeout:
            delivery.mark_failure("Request timed out after 30 seconds")
        except requests.exceptions.ConnectionError as e:
            delivery.mark_failure(f"Connection error: {str(e)[:200]}")
        except requests.exceptions.RequestException as e:
            delivery.mark_failure(f"Request failed: {str(e)[:200]}")
        except Exception as e:
            delivery.mark_failure(f"Unexpected error: {str(e)[:200]}")


def send_test_webhook(subscription) -> dict:
    """
    Send a test webhook to verify the subscription endpoint.

    Args:
        subscription: WebhookSubscription instance

    Returns:
        Dict with 'success', 'status_code', and 'message' keys
    """
    test_data = {
        'message': 'This is a test webhook from SmoothSchedule',
        'subscription_id': str(subscription.id),
        'timestamp': timezone.now().isoformat(),
    }

    payload = create_webhook_payload('test', test_data)
    payload_json = json.dumps(payload, separators=(',', ':'))

    timestamp = int(time.time())
    signature = generate_signature(payload_json, subscription.secret, timestamp)

    headers = {
        'Content-Type': 'application/json',
        'User-Agent': 'SmoothSchedule-Webhook/1.0',
        'X-Webhook-ID': payload['id'],
        'X-Webhook-Timestamp': str(timestamp),
        'X-Webhook-Signature': signature,
        'X-Webhook-Test': 'true',
    }

    try:
        response = requests.post(
            subscription.url,
            data=payload_json,
            headers=headers,
            timeout=30,
        )

        if 200 <= response.status_code < 300:
            return {
                'success': True,
                'status_code': response.status_code,
                'message': 'Test webhook delivered successfully',
            }
        else:
            return {
                'success': False,
                'status_code': response.status_code,
                'message': f'Endpoint returned HTTP {response.status_code}',
            }

    except requests.exceptions.Timeout:
        return {
            'success': False,
            'status_code': None,
            'message': 'Request timed out after 30 seconds',
        }
    except requests.exceptions.ConnectionError:
        return {
            'success': False,
            'status_code': None,
            'message': 'Could not connect to the webhook URL',
        }
    except Exception as e:
        return {
            'success': False,
            'status_code': None,
            'message': f'Error: {str(e)[:200]}',
        }
