"""
Webhook Processing Worker Tasks
Handles webhook delivery and retry logic
"""
import logging
from datetime import datetime, timedelta
from typing import Optional, Dict, Any, List
import json
import hashlib
import hmac
import requests

from celery import shared_task
from sqlalchemy import select, and_

from app.workers.celery_app import celery
from app.database import get_sync_session
from app.models.webhook import ClientWebhook, WebhookDeliveryLog, WebhookEventType

logger = logging.getLogger(__name__)


# Webhook retry configuration
RETRY_DELAYS = [60, 300, 900, 3600, 7200]  # 1min, 5min, 15min, 1hr, 2hr


@celery.task(
    bind=True,
    name="app.workers.tasks.webhook_tasks.deliver_webhook",
    max_retries=5,
    queue="default"
)
def deliver_webhook(
    self,
    webhook_id: int,
    event_type: str,
    payload: Dict[str, Any],
    delivery_log_id: Optional[int] = None
) -> Dict[str, Any]:
    """
    Deliver a webhook to the configured endpoint

    Args:
        webhook_id: ClientWebhook ID
        event_type: Type of event
        payload: Event payload
        delivery_log_id: Existing delivery log ID (for retries)
    """
    try:
        logger.info(f"Delivering webhook {webhook_id} for event {event_type}")

        with get_sync_session() as session:
            webhook = session.execute(
                select(ClientWebhook)
                .where(ClientWebhook.id == webhook_id)
            ).scalar_one_or_none()

            if not webhook:
                return {"success": False, "error": "Webhook not found"}

            if not webhook.is_active:
                return {"success": False, "error": "Webhook is inactive"}

            # Check if event type is subscribed
            if event_type not in webhook.events:
                return {"success": False, "error": "Event type not subscribed"}

            # Create or update delivery log
            if delivery_log_id:
                delivery_log = session.execute(
                    select(WebhookDeliveryLog)
                    .where(WebhookDeliveryLog.id == delivery_log_id)
                ).scalar_one_or_none()
            else:
                delivery_log = WebhookDeliveryLog(
                    webhook_id=webhook_id,
                    event_type=WebhookEventType(event_type),
                    payload=payload,
                    status="pending"
                )
                session.add(delivery_log)
                session.flush()

            # Prepare request
            timestamp = datetime.utcnow().isoformat()
            body = json.dumps(payload)

            # Generate signature
            signature = _generate_signature(
                body,
                timestamp,
                webhook.secret_key
            )

            headers = {
                "Content-Type": "application/json",
                "X-Webhook-Signature": signature,
                "X-Webhook-Timestamp": timestamp,
                "X-Webhook-Event": event_type,
                "X-Webhook-Delivery": str(delivery_log.id),
                "User-Agent": "AI360-Webhooks/1.0"
            }

            # Add custom headers
            if webhook.headers:
                headers.update(webhook.headers)

            # Make request
            delivery_log.attempted_at = datetime.utcnow()
            delivery_log.attempt_count = (delivery_log.attempt_count or 0) + 1

            try:
                response = requests.post(
                    webhook.url,
                    data=body,
                    headers=headers,
                    timeout=webhook.timeout_seconds or 30
                )

                delivery_log.response_status = response.status_code
                delivery_log.response_body = response.text[:10000]  # Limit response size
                delivery_log.response_time_ms = int(response.elapsed.total_seconds() * 1000)

                if 200 <= response.status_code < 300:
                    delivery_log.status = "delivered"
                    delivery_log.delivered_at = datetime.utcnow()

                    # Update webhook stats
                    webhook.last_triggered_at = datetime.utcnow()
                    webhook.success_count = (webhook.success_count or 0) + 1

                    session.commit()

                    logger.info(f"Webhook {webhook_id} delivered successfully")

                    return {
                        "success": True,
                        "delivery_log_id": delivery_log.id,
                        "status_code": response.status_code
                    }
                else:
                    # Non-2xx response, schedule retry
                    raise Exception(f"HTTP {response.status_code}: {response.text[:500]}")

            except requests.exceptions.RequestException as e:
                delivery_log.status = "failed"
                delivery_log.error_message = str(e)
                webhook.failure_count = (webhook.failure_count or 0) + 1
                session.commit()
                raise

    except Exception as e:
        logger.error(f"Error delivering webhook {webhook_id}: {str(e)}")

        # Get retry delay based on attempt count
        retry_count = self.request.retries
        if retry_count < len(RETRY_DELAYS):
            countdown = RETRY_DELAYS[retry_count]
            self.retry(exc=e, countdown=countdown)
        else:
            # Max retries reached, mark as failed
            with get_sync_session() as session:
                if delivery_log_id:
                    delivery_log = session.execute(
                        select(WebhookDeliveryLog)
                        .where(WebhookDeliveryLog.id == delivery_log_id)
                    ).scalar_one_or_none()

                    if delivery_log:
                        delivery_log.status = "failed"
                        delivery_log.error_message = f"Max retries exceeded: {str(e)}"
                        session.commit()

            return {
                "success": False,
                "error": f"Max retries exceeded: {str(e)}"
            }


@celery.task(
    bind=True,
    name="app.workers.tasks.webhook_tasks.process_pending_webhooks",
    max_retries=1,
    queue="default"
)
def process_pending_webhooks(self) -> Dict[str, Any]:
    """
    Process pending webhook deliveries that need retry
    """
    try:
        logger.info("Processing pending webhooks")

        with get_sync_session() as session:
            # Find failed deliveries that need retry
            retry_cutoff = datetime.utcnow() - timedelta(hours=24)

            pending = session.execute(
                select(WebhookDeliveryLog)
                .where(
                    and_(
                        WebhookDeliveryLog.status == "failed",
                        WebhookDeliveryLog.attempt_count < 5,
                        WebhookDeliveryLog.created_at > retry_cutoff
                    )
                )
                .limit(100)
            ).scalars().all()

            results = {
                "pending_found": len(pending),
                "retried": 0,
                "errors": []
            }

            for delivery in pending:
                try:
                    # Check if enough time has passed since last attempt
                    retry_index = min(delivery.attempt_count - 1, len(RETRY_DELAYS) - 1)
                    retry_delay = RETRY_DELAYS[retry_index]

                    if delivery.attempted_at:
                        next_retry = delivery.attempted_at + timedelta(seconds=retry_delay)
                        if datetime.utcnow() < next_retry:
                            continue

                    # Queue retry
                    deliver_webhook.delay(
                        webhook_id=delivery.webhook_id,
                        event_type=delivery.event_type.value,
                        payload=delivery.payload,
                        delivery_log_id=delivery.id
                    )
                    results["retried"] += 1

                except Exception as e:
                    results["errors"].append({
                        "delivery_id": delivery.id,
                        "error": str(e)
                    })

            logger.info(f"Pending webhooks processed: {results}")
            return results

    except Exception as e:
        logger.error(f"Error processing pending webhooks: {str(e)}")
        self.retry(exc=e)


@celery.task(
    name="app.workers.tasks.webhook_tasks.trigger_event",
    queue="default"
)
def trigger_event(
    client_id: int,
    event_type: str,
    payload: Dict[str, Any]
) -> Dict[str, Any]:
    """
    Trigger a webhook event for a client

    Args:
        client_id: Client ID
        event_type: Type of event
        payload: Event payload
    """
    try:
        logger.info(f"Triggering event {event_type} for client {client_id}")

        with get_sync_session() as session:
            # Find active webhooks subscribed to this event
            webhooks = session.execute(
                select(ClientWebhook)
                .where(
                    and_(
                        ClientWebhook.client_id == client_id,
                        ClientWebhook.is_active == True
                    )
                )
            ).scalars().all()

            results = {
                "webhooks_found": len(webhooks),
                "webhooks_triggered": 0
            }

            for webhook in webhooks:
                # Check if webhook is subscribed to this event
                if event_type in (webhook.events or []):
                    deliver_webhook.delay(
                        webhook_id=webhook.id,
                        event_type=event_type,
                        payload=payload
                    )
                    results["webhooks_triggered"] += 1

            logger.info(f"Event triggered: {results}")
            return results

    except Exception as e:
        logger.error(f"Error triggering event: {str(e)}")
        return {"success": False, "error": str(e)}


@celery.task(
    name="app.workers.tasks.webhook_tasks.test_webhook",
    queue="default"
)
def test_webhook(webhook_id: int) -> Dict[str, Any]:
    """
    Send a test webhook to verify configuration

    Args:
        webhook_id: ClientWebhook ID
    """
    try:
        logger.info(f"Testing webhook {webhook_id}")

        test_payload = {
            "event": "webhook.test",
            "timestamp": datetime.utcnow().isoformat(),
            "message": "This is a test webhook from AI360 Solutions",
            "webhook_id": webhook_id
        }

        result = deliver_webhook.apply(
            args=[webhook_id, "webhook.test", test_payload]
        )

        return result.get()

    except Exception as e:
        logger.error(f"Error testing webhook: {str(e)}")
        return {"success": False, "error": str(e)}


def _generate_signature(body: str, timestamp: str, secret: str) -> str:
    """Generate HMAC-SHA256 signature for webhook payload"""
    message = f"{timestamp}.{body}"
    signature = hmac.new(
        secret.encode("utf-8"),
        message.encode("utf-8"),
        hashlib.sha256
    ).hexdigest()

    return f"sha256={signature}"
