"""
Celery task: dunning payment retry.

Task name: subscription.retry_subscription_payment
Scheduled by: subscription listener when a charge fails (via apply_async with eta).

On success:
  - Reset dunning state
  - Restore ACTIVE status

On failure:
  - Increment dunning counter
  - Schedule next retry or exhaust dunning
"""

import logging
from datetime import datetime, timedelta, timezone

from celery.utils.log import get_task_logger

from src.worker.celery_app import celery_app
from src.core.database import SessionCelery

logger = get_task_logger(__name__)

MAX_DUNNING_RETRIES = 3


@celery_app.task(name="subscription.retry_subscription_payment", bind=True, max_retries=0)
def retry_subscription_payment(
    self,
    subscription_id: str,
    invoice_id: str,
) -> dict:
    """
    Re-attempt payment for a failed subscription invoice.

    Args:
        subscription_id: opaque subscription_id string
        invoice_id: opaque invoice_id string

    Returns:
        Dict with outcome: "success" | "failed" | "exhausted"
    """
    logger.info(
        "retry_subscription_payment: subscription=%s invoice=%s",
        subscription_id, invoice_id
    )

    try:
        from sqlalchemy import select
        from src.apps.subscriptions.models.subscription import Subscription
        from src.apps.subscriptions.enums import SubscriptionStatus, SubscriptionActivityTypes
        from src.apps.subscriptions import crud as sub_crud
        from src.apps.invoices.models.invoice import Invoice
        from src.core.utils.enums import InvoiceStatusTypes

        with SessionCelery() as db:
            # Load subscription
            sub_stmt = select(Subscription).where(
                Subscription.subscription_id == subscription_id,
                Subscription.deleted_at == None,
            )
            subscription = db.execute(sub_stmt).scalar_one_or_none()

            if subscription is None:
                logger.warning("retry_subscription_payment: subscription %s not found", subscription_id)
                return {"outcome": "not_found"}

            # Load invoice
            inv_stmt = select(Invoice).where(
                Invoice.invoice_id == invoice_id,
                Invoice.deleted_at == None,
            )
            invoice = db.execute(inv_stmt).scalar_one_or_none()

            if invoice is None:
                logger.warning("retry_subscription_payment: invoice %s not found", invoice_id)
                return {"outcome": "not_found"}

            # VULN-003: Validate that the invoice actually belongs to this subscription
            if invoice.subscription_id != subscription.id:
                logger.error(
                    "retry_subscription_payment: invoice %s does not belong to subscription %s "
                    "(invoice.subscription_id=%s, subscription.id=%s)",
                    invoice_id,
                    subscription_id,
                    invoice.subscription_id,
                    subscription.id,
                )
                return {"outcome": "scope_error", "error": "invoice_subscription_mismatch"}

            # Only retry if invoice is still in a retryable status
            if invoice.status not in (
                InvoiceStatusTypes.FAILED,
                InvoiceStatusTypes.PENDING,
                InvoiceStatusTypes.OVERDUE,
            ):
                logger.info(
                    "retry_subscription_payment: invoice %s already in status %d, skipping",
                    invoice_id, invoice.status
                )
                return {"outcome": "skipped", "reason": "invoice not in retryable status"}

            # Resolve the pending transaction linked to this invoice
            from sqlalchemy import select as _sel
            from src.apps.transactions.models.transactions import Transactions, transactions_invoices_map

            txn_stmt = (
                _sel(Transactions)
                .join(transactions_invoices_map, transactions_invoices_map.c.transaction_id == Transactions.id)
                .where(
                    transactions_invoices_map.c.invoice_id == invoice.id,
                    Transactions.txn_status == InvoiceStatusTypes.PENDING,
                )
                .order_by(Transactions.id.desc())
                .limit(1)
            )
            pending_txn = db.execute(txn_stmt).scalar_one_or_none()

            now = datetime.now(timezone.utc)
            subscription.dunning_last_retry_at = now
            subscription.updated_at = now
            db.flush()

            sub_crud.write_activity(
                db=db,
                subscription_id=subscription.id,
                activity_type=SubscriptionActivityTypes.DUNNING_RETRY_ATTEMPTED,
                description=f"Dunning retry #{subscription.dunning_retry_count} submitted",
                actor_type="system",
                actor_id=None,
                metadata={
                    "invoice_id": invoice_id,
                    "retry_number": subscription.dunning_retry_count,
                },
            )

            db.commit()

            # Submit charge via process_scheduled_payment outside the DB session
            if pending_txn is not None and pending_txn.payment_method_id is not None:
                from src.worker.hpp_tasks import process_scheduled_payment
                process_scheduled_payment.apply_async(
                    args=[pending_txn.id, pending_txn.payment_method_id],
                )
                logger.info(
                    "retry_subscription_payment: process_scheduled_payment queued "
                    "for txn %s subscription=%s invoice=%s",
                    pending_txn.txn_id, subscription_id, invoice_id,
                )
                return {"outcome": "submitted", "invoice_id": invoice_id}
            else:
                logger.warning(
                    "retry_subscription_payment: no pending transaction or payment method "
                    "for invoice %s — cannot submit charge",
                    invoice_id,
                )
                return {"outcome": "no_transaction", "invoice_id": invoice_id}

    except Exception as exc:
        logger.error(
            "retry_subscription_payment: error for subscription=%s: %s",
            subscription_id, exc,
            exc_info=True,
        )
        return {"outcome": "error", "error": str(exc)}
