# app/modules/loyalty/tasks/wallet_sync.py
"""
Wallet synchronization task.

Handles syncing loyalty card data to Google Wallet and Apple Wallet
for cards that may have missed real-time updates.

Uses exponential backoff (1s, 4s, 16s) per card to handle transient
API failures without blocking the entire batch.
"""

import logging
import time

from celery import shared_task

logger = logging.getLogger(__name__)

# Exponential backoff delays in seconds: 1s, 4s, 16s
_RETRY_DELAYS = [1, 4, 16]
_MAX_ATTEMPTS = len(_RETRY_DELAYS) + 1  # 4 total attempts


@shared_task(name="loyalty.sync_wallet_passes")
def sync_wallet_passes() -> dict:
    """
    Sync wallet passes for cards that may be out of sync.

    This catches any cards that missed real-time updates due to
    errors or network issues.

    Returns:
        Summary of synced passes
    """
    from datetime import UTC, datetime, timedelta

    from app.core.database import SessionLocal
    from app.modules.loyalty.models import LoyaltyCard, LoyaltyTransaction
    from app.modules.loyalty.services import wallet_service

    db = SessionLocal()
    try:
        # Find cards with transactions in the last hour that have wallet IDs
        one_hour_ago = datetime.now(UTC) - timedelta(hours=1)

        recent_tx_card_ids = (
            db.query(LoyaltyTransaction.card_id)
            .filter(LoyaltyTransaction.transaction_at >= one_hour_ago)
            .distinct()
            .all()
        )
        card_ids = [row[0] for row in recent_tx_card_ids]

        if not card_ids:
            logger.info("No cards with recent transactions to sync")
            return {
                "status": "success",
                "cards_checked": 0,
                "google_synced": 0,
                "apple_synced": 0,
                "failed_card_ids": [],
            }

        cards = (
            db.query(LoyaltyCard)
            .filter(
                LoyaltyCard.id.in_(card_ids),
                (LoyaltyCard.google_object_id.isnot(None))
                | (LoyaltyCard.apple_serial_number.isnot(None)),
            )
            .all()
        )

        google_synced = 0
        apple_synced = 0
        failed_card_ids = []

        for card in cards:
            success, google, apple = _sync_card_with_backoff(
                wallet_service, db, card
            )
            if success:
                google_synced += google
                apple_synced += apple
            else:
                failed_card_ids.append(card.id)

        if failed_card_ids:
            logger.error(
                f"Wallet sync: {len(failed_card_ids)} cards failed after "
                f"{_MAX_ATTEMPTS} attempts each: {failed_card_ids}"
            )

        logger.info(
            f"Wallet sync complete: {len(cards)} cards checked, "
            f"{google_synced} Google, {apple_synced} Apple, "
            f"{len(failed_card_ids)} failed"
        )

        return {
            "status": "success",
            "cards_checked": len(cards),
            "google_synced": google_synced,
            "apple_synced": apple_synced,
            "failed_card_ids": failed_card_ids,
        }

    except Exception as e:
        logger.error(f"Wallet sync task failed: {e}")
        return {
            "status": "error",
            "error": str(e),
            "failed_card_ids": [],
        }
    finally:
        db.close()


def _sync_card_with_backoff(wallet_service, db, card) -> tuple[bool, int, int]:
    """Sync a single card with exponential backoff.

    Returns (success, google_count, apple_count).
    """
    last_error = None

    for attempt in range(_MAX_ATTEMPTS):
        try:
            results = wallet_service.sync_card_to_wallets(db, card)
            google = 1 if results.get("google_wallet") else 0
            apple = 1 if results.get("apple_wallet") else 0
            return True, google, apple
        except Exception as e:
            last_error = e
            if attempt < len(_RETRY_DELAYS):
                delay = _RETRY_DELAYS[attempt]
                logger.warning(
                    f"Card {card.id} sync failed (attempt {attempt + 1}/"
                    f"{_MAX_ATTEMPTS}), retrying in {delay}s: {e}"
                )
                time.sleep(delay)

    logger.error(
        f"Card {card.id} sync failed after {_MAX_ATTEMPTS} attempts: "
        f"{last_error}"
    )
    return False, 0, 0
