"""
HPP Celery Tasks — background processing for scheduled installment payments.

Tasks:
  process_scheduled_payment   — Run a scheduled (future-dated) installment payment.
                                 Called when a split/recurring payment's billing date arrives.
"""

import logging
import uuid
from typing import Any

from celery.utils.log import get_task_logger

from src.worker.celery_app import celery_app

logger = get_task_logger(__name__)


@celery_app.task(
    bind=True,
    name="process_scheduled_payment",
    max_retries=3,
    default_retry_delay=300,  # 5 minutes between retries
)
def process_scheduled_payment(
    self: Any,
    transaction_id: int,
    payment_method_id: int,
) -> dict:
    """
    Process a single scheduled installment payment.

    This task is queued at HPP submit time for each future-dated installment.
    On execution it:
      1. Fetches the transaction (guard: skip if already PAID)
      2. Submits a mock charge (or real provider call in production)
      3. On success: updates transaction to PAID, emits transaction.completed
      4. On failure: updates transaction to FAILED, emits transaction.failed

    C-9: The mock charge outcome is controlled by settings.PAYMENT_STUB_ALWAYS_SUCCEED.
    When that flag is False (and PAYMENT_PROVIDER_STUB=True), a ~20 % random
    failure is simulated so the transaction.failed path can be exercised during
    testing.  This is TEST-ONLY behaviour — in production PAYMENT_PROVIDER_STUB
    must be False and the real provider decides success/failure.

    Args:
        transaction_id:    ID of the Transactions row to process.
        payment_method_id: ID of the PaymentMethod to charge.

    Returns:
        Dict with outcome details.
    """
    import asyncio
    import random

    from src.core.config import settings
    from src.core.database import SessionCelery
    from src.core.utils.enums import TransactionStatusTypes
    from src.events.base import BaseEvent
    from src.events.dispatcher import EventDispatcher

    logger.info(
        "process_scheduled_payment started: transaction_id=%s pm_id=%s",
        transaction_id,
        payment_method_id,
    )

    try:
        with SessionCelery() as db:
            from sqlalchemy import select
            from src.apps.transactions.models.transactions import Transactions

            stmt = select(Transactions).where(Transactions.id == transaction_id)
            txn = db.execute(stmt).scalar_one_or_none()

            if txn is None:
                logger.warning(
                    "process_scheduled_payment: transaction %s not found — skipping",
                    transaction_id,
                )
                return {"status": "skipped", "reason": "transaction_not_found"}

            # Guard: already processed
            if txn.txn_status == TransactionStatusTypes.PAID:
                logger.info(
                    "process_scheduled_payment: transaction %s already PAID — skipping",
                    transaction_id,
                )
                return {"status": "skipped", "reason": "already_paid"}

            # Mock charge: generate synthetic txn_id
            mock_txn_id = f"mock_{uuid.uuid4().hex[:12]}"

            # In production this would call the payment provider:
            # result = provider.submit_charge(payment_method, txn.txn_amount, metadata)
            #
            # C-9: Stub mode charge outcome.
            # PAYMENT_STUB_ALWAYS_SUCCEED=True  → always succeed (default/safe).
            # PAYMENT_STUB_ALWAYS_SUCCEED=False → simulate ~20 % failure rate so
            #   the transaction.failed event path can be tested end-to-end.
            # NOTE: This random failure is TEST-ONLY. In production,
            #   PAYMENT_PROVIDER_STUB=False and the real provider decides outcome.
            if settings.PAYMENT_PROVIDER_STUB and not settings.PAYMENT_STUB_ALWAYS_SUCCEED:
                # ~20 % simulated failure for testing
                charge_success = random.random() > 0.20  # noqa: S311 — test-only RNG
                if not charge_success:
                    logger.info(
                        "process_scheduled_payment: TEST stub simulated failure "
                        "for transaction %s (PAYMENT_STUB_ALWAYS_SUCCEED=False)",
                        transaction_id,
                    )
            else:
                charge_success = True

            # Resolve linked invoice_id (present for subscription invoices)
            linked_invoice_id = (txn.txn_metadata or {}).get("invoice_id")

            if charge_success:
                txn.txn_status = TransactionStatusTypes.PAID
                txn.txn_id = mock_txn_id

                event_type = "transaction.completed"
                event_data = {
                    "transaction_id": txn.id,
                    "txn_id": txn.txn_id,
                    "txn_amount": float(txn.txn_amount or 0),
                    "payment_request_id": txn.payment_request_id,
                    "merchant_id": txn.merchant_id,
                    "customer_id": txn.customer_id,
                    "invoice_id": linked_invoice_id,
                    "is_scheduled": True,
                }
                result_status = "paid"
            else:
                txn.txn_status = TransactionStatusTypes.FAILED

                event_type = "transaction.failed"
                event_data = {
                    "transaction_id": txn.id,
                    "txn_amount": float(txn.txn_amount or 0),
                    "payment_request_id": txn.payment_request_id,
                    "merchant_id": txn.merchant_id,
                    "customer_id": txn.customer_id,
                    "invoice_id": linked_invoice_id,
                }
                result_status = "failed"

        # Dispatch event outside the session context to avoid transaction issues
        loop = asyncio.new_event_loop()
        try:
            loop.run_until_complete(
                EventDispatcher.dispatch(
                    BaseEvent(event_type=event_type, data=event_data)
                )
            )
        finally:
            loop.close()

        logger.info(
            "process_scheduled_payment finished: transaction_id=%s status=%s",
            transaction_id,
            result_status,
        )
        return {"status": result_status, "transaction_id": transaction_id}

    except Exception as exc:
        logger.error(
            "process_scheduled_payment failed for transaction %s: %s",
            transaction_id,
            exc,
        )
        raise self.retry(exc=exc)


__all__ = ["process_scheduled_payment"]
