"""Reward engine for processing referral rewards."""

from __future__ import annotations

from collections.abc import Awaitable, Callable
from datetime import datetime, timezone
from uuid import uuid4

from referral.config import get_referral
from referral.models import (
    Referral,
    ReferralReward,
    ReferralStatus,
    RewardStatus,
    RewardType,
)
from referral.store import ReferralStore

# Type alias for the optional hook invoked after a reward is granted.
type RewardCallback = Callable[[ReferralReward], Awaitable[None]]


class RewardEngine:
    """Processes referral rewards with optional product-specific hooks.

    Args:
        store: The persistence backend.
        on_reward_granted: Async callback invoked after each reward is granted.
    """

    def __init__(
        self,
        store: ReferralStore,
        *,
        on_reward_granted: RewardCallback | None = None,
    ) -> None:
        self.store = store
        self.on_reward_granted = on_reward_granted

    async def grant_reward(
        self,
        referral: Referral,
        *,
        reward_type: RewardType | None = None,
        amount: float | None = None,
        user_id: str | None = None,
    ) -> ReferralReward:
        """Create and persist a reward record for a completed referral.

        Falls back to the global ``ReferralConfig`` values for reward_type
        and amount when not explicitly provided.

        Args:
            referral: The referral that triggered this reward.
            reward_type: Override the configured reward type.
            amount: Override the configured reward amount.
            user_id: Override the recipient (defaults to referrer).

        Returns:
            The created ReferralReward.
        """
        config = get_referral()
        resolved_type = reward_type or config.reward_type
        resolved_amount = amount if amount is not None else config.reward_amount
        resolved_user = user_id or referral.referrer_id

        now = datetime.now(timezone.utc)
        reward = ReferralReward(
            id=uuid4(),
            user_id=resolved_user,
            referral_id=referral.id,
            reward_type=resolved_type,
            amount=resolved_amount,
            status=RewardStatus.GRANTED,
            granted_at=now,
        )
        saved = await self.store.save_reward(reward)

        if self.on_reward_granted is not None:
            await self.on_reward_granted(saved)

        return saved

    async def grant_double_sided(
        self,
        referral: Referral,
        *,
        reward_type: RewardType | None = None,
        amount: float | None = None,
    ) -> tuple[ReferralReward, ReferralReward]:
        """Grant rewards to both the referrer and the referred user.

        Args:
            referral: The completed referral.
            reward_type: Override the configured reward type.
            amount: Override the configured reward amount.

        Returns:
            Tuple of (referrer_reward, referred_reward).
        """
        referrer_reward = await self.grant_reward(
            referral, reward_type=reward_type, amount=amount, user_id=referral.referrer_id
        )
        referred_reward = await self.grant_reward(
            referral, reward_type=reward_type, amount=amount, user_id=referral.referred_id
        )
        return referrer_reward, referred_reward

    async def process_completed_referrals(
        self,
        referrals: list[Referral],
    ) -> list[ReferralReward]:
        """Batch-process a list of completed referrals, granting rewards.

        Only referrals in COMPLETED status are processed. After granting
        the reward, the referral status is updated to REWARDED.

        Args:
            referrals: Referrals to process.

        Returns:
            List of all granted rewards.
        """
        config = get_referral()
        granted: list[ReferralReward] = []

        for referral in referrals:
            if referral.status != ReferralStatus.COMPLETED:
                continue

            if config.double_sided:
                r1, r2 = await self.grant_double_sided(referral)
                granted.extend([r1, r2])
            else:
                reward = await self.grant_reward(referral)
                granted.append(reward)

            referral.status = ReferralStatus.REWARDED
            await self.store.update_referral(referral)

        return granted

    async def process_pending_rewards(self) -> list[ReferralReward]:
        """Find all pending rewards and mark them as granted.

        This is useful when rewards are queued and need batch settlement.

        Returns:
            List of newly granted rewards.
        """
        pending = await self.store.get_pending_rewards()
        processed: list[ReferralReward] = []

        for reward in pending:
            reward.status = RewardStatus.GRANTED
            reward.granted_at = datetime.now(timezone.utc)
            updated = await self.store.update_reward(reward)
            processed.append(updated)

            if self.on_reward_granted is not None:
                await self.on_reward_granted(updated)

        return processed
