from celery import shared_task
from sqlalchemy.orm import Session
from app.db.session import SessionLocal
from app.db.models import Delivery, Notification, Customer
from app.channels.telegram import TelegramAdapter
from app.channels.vk import VKAdapter
from app.channels.email import EmailAdapter
# from app.channels.whatsapp import WhatsAppAdapter
from app.services.template_renderer import TemplateRenderer
from app.services.rate_limiter import RateLimiter
from app.core.config import get_settings
import asyncio


@shared_task(bind=True, name="app.workers.deliveries.deliver_message", max_retries=3)
def deliver_message(self, delivery_id: int) -> str:
    """
    Deliver a single message via appropriate channel adapter
    
    Args:
        delivery_id: Delivery record ID
    
    Retry policy: exponential backoff, max 3 attempts
    On failure: updates delivery.status and last_error, moves to DLQ if exhausted
    """
    db: Session = SessionLocal()
    
    try:
        delivery = db.query(Delivery).filter(Delivery.id == delivery_id).first()
        if not delivery:
            return f"delivery:{delivery_id}:not_found"
        
        # Get notification and customer
        notification = db.query(Notification).filter(Notification.id == delivery.notification_id).first()
        if not notification:
            delivery.status = "failed"
            delivery.last_error = "Notification not found"
            db.commit()
            return f"delivery:{delivery_id}:no_notification"
        
        # Render template
        renderer = TemplateRenderer()
        content = renderer.render(notification.template.content, notification.payload)
        
        # Select channel adapter
        adapters = {
            "telegram": TelegramAdapter(),
            "vk": VKAdapter(),
            "email": EmailAdapter(),
            # "whatsapp": WhatsAppAdapter(),
        }
        
        adapter = adapters.get(delivery.channel)
        if not adapter:
            delivery.status = "failed"
            delivery.last_error = f"Unknown channel: {delivery.channel}"
            db.commit()
            return f"delivery:{delivery_id}:unknown_channel"
        
        # Rate limiting: token bucket per tenant + channel
        settings = get_settings()
        limiter = RateLimiter()
        rate_key = f"tenant:{notification.tenant_id}:{delivery.channel}"
        allowed = limiter.check_and_consume(rate_key, tokens=1)
        if not allowed:
            # If rate limited, retry after a short backoff based on refill rate
            # Ensure we do not increment attempts nor mark as failed yet
            backoff_seconds = max(1, int(1 / settings.RATE_LIMIT_REFILL_RATE_PER_SEC))
            raise self.retry(countdown=backoff_seconds)

        # Send message (async call wrapped in sync task)
        result = asyncio.run(adapter.send(delivery.target, content, tenant_id=notification.tenant_id))
        
        delivery.attempts += 1
        
        if result.success:
            delivery.status = "sent"
            delivery.sent_at = db.func.now()
            delivery.response_meta = result.response_meta or {}
            db.commit()
            return f"delivery:{delivery_id}:success"
        else:
            # Detect provider rate limit to apply longer backoff
            error_text = (result.error or "").lower()
            if "rate limit" in error_text or "too many requests" in error_text:
                # Apply cautious backoff without marking DLQ yet
                backoff = min(60, 5 * (delivery.attempts + 1))
                db.commit()
                raise self.retry(countdown=backoff)

            delivery.status = "failed"
            delivery.last_error = result.error
            db.commit()
            
            # Retry if attempts < max
            if delivery.attempts < 3:
                raise self.retry(countdown=2 ** delivery.attempts)  # exponential backoff
            else:
                # Move to DLQ
                from app.db.models import DLQ
                dlq_entry = DLQ(
                    tenant_id=notification.tenant_id,
                    notification_id=notification.id,
                    channel=delivery.channel,
                    reason=f"Max retries exceeded: {result.error}",
                    payload={"delivery_id": delivery_id, "target": delivery.target}
                )
                db.add(dlq_entry)
                db.commit()
                return f"delivery:{delivery_id}:exhausted"
    
    except Exception as e:
        delivery = db.query(Delivery).filter(Delivery.id == delivery_id).first()
        if delivery:
            delivery.attempts += 1
            delivery.status = "failed"
            delivery.last_error = str(e)
            db.commit()
        
        raise self.retry(exc=e, countdown=2 ** (delivery.attempts if delivery else 1))
    
    finally:
        db.close()
