from datetime import datetime, timedelta, timezone

from sqlalchemy import and_, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.models import PromoCode
from app.models.coupon_mall_claim import CouponMallClaim, CouponMallClaimStatus
from app.models.notification import NotificationEvent
from app.models.point_log import PointChangeType
from app.models.promo_code import PromoCodeType
from app.services import membership_service, notification_service


def _max_discount_per_use(coupon: PromoCode) -> int:
    """单张券最大优惠金额（分），用于会员券预扣/退还积分。"""
    coupon_type = str(getattr(coupon, "type", "") or "")
    if coupon_type == PromoCodeType.PERCENT.value:
        # 百分比券理论上没有上限（除非有 max_discount_amount），会员不应创建此类券
        return int(getattr(coupon, "max_discount_amount", 0) or 0)
    if coupon_type == PromoCodeType.OVERRIDE.value:
        # 覆盖价也不应被会员创建
        return int(coupon.value or 0)
    # fixed / threshold_fixed：value 就是最大优惠金额
    return int(coupon.value or 0)


def coupon_refundable_points(coupon: PromoCode) -> int:
    if getattr(coupon, "issuer_type", None) != "member":
        return 0
    point_cost = int(getattr(coupon, "point_cost", 0) or 0)
    if point_cost <= 0:
        return 0
    used_count = max(0, int(coupon.used_count or 0))
    max_discount = _max_discount_per_use(coupon)
    used_cost = max_discount * used_count * membership_service.POINTS_TO_CENTS_RATE // 100
    refunded = int(getattr(coupon, "point_refunded", 0) or 0)
    return max(0, point_cost - used_cost - refunded)


async def refund_unused_member_coupon(
    db: AsyncSession,
    coupon: PromoCode,
    *,
    reason: str,
) -> int:
    coupon.is_enabled = False
    refund_points = coupon_refundable_points(coupon)
    if refund_points <= 0:
        return 0

    user_id = getattr(coupon, "issuer_user_id", None)
    if not user_id:
        return 0

    await membership_service.add_points(
        db=db,
        user_id=int(user_id),
        delta=refund_points,
        change_type=PointChangeType.ACTIVITY_GIFT,
        ref_no=f"{coupon.code}:refund",
        remark=f"会员优惠券未使用次数返还积分：{coupon.code}，{reason}",
    )
    coupon.point_refunded = int(getattr(coupon, "point_refunded", 0) or 0) + refund_points
    await notification_service.safe_notify(
        db,
        user_id=int(user_id),
        category="promotion",
        title="优惠券未使用积分已返还",
        content=f"优惠券 {coupon.code} 已{reason}，返还 {refund_points} 积分。",
        link="/promotion",
    )
    return refund_points


async def refund_expired_member_coupons(
    db: AsyncSession,
    *,
    user_id: int | None = None,
    limit: int = 100,
) -> dict[str, int]:
    now = datetime.now(timezone.utc)
    stmt = (
        select(PromoCode)
        .where(
            PromoCode.issuer_type == "member",
            PromoCode.is_enabled.is_(True),
            PromoCode.expires_at.is_not(None),
            PromoCode.expires_at <= now,
        )
        .order_by(PromoCode.expires_at.asc(), PromoCode.id.asc())
        .limit(limit)
        .with_for_update()
    )
    if user_id is not None:
        stmt = stmt.where(PromoCode.issuer_user_id == user_id)

    result = await db.execute(stmt)
    coupons = list(result.scalars().all())
    refunded_points = 0
    refunded_count = 0
    for coupon in coupons:
        points = await refund_unused_member_coupon(db, coupon, reason="到期")
        if points > 0:
            refunded_count += 1
            refunded_points += points

    if coupons:
        await db.flush()
    return {
        "checked": len(coupons),
        "refunded_count": refunded_count,
        "refunded_points": refunded_points,
    }


async def notify_expiring_coupons(
    db: AsyncSession,
    *,
    hours_before: int = 24,
    limit: int = 200,
) -> dict[str, int]:
    """给即将过期的已领取优惠券发送站内信提醒，使用 NotificationEvent 去重。"""
    now = datetime.now(timezone.utc)
    deadline = now + timedelta(hours=hours_before)

    # 已领取且未使用、关联优惠券有有效期且在未来指定窗口内的领取记录
    stmt = (
        select(CouponMallClaim, PromoCode)
        .join(PromoCode, PromoCode.id == CouponMallClaim.promo_code_id)
        .where(
            CouponMallClaim.status == CouponMallClaimStatus.CLAIMED,
            PromoCode.expires_at.is_not(None),
            PromoCode.expires_at > now,
            PromoCode.expires_at <= deadline,
        )
        .order_by(PromoCode.expires_at.asc())
        .limit(limit)
    )
    result = await db.execute(stmt)
    rows = list(result.all())

    notified = 0
    skipped = 0
    for claim, coupon in rows:
        event_key = f"coupon_expire_reminder:{claim.user_id}:{claim.id}"
        exists = await db.scalar(
            select(NotificationEvent.id).where(NotificationEvent.event_key == event_key)
        )
        if exists:
            skipped += 1
            continue

        expires_local = coupon.expires_at.astimezone().strftime("%Y-%m-%d %H:%M") if coupon.expires_at else "-"
        await notification_service.safe_notify(
            db,
            user_id=claim.user_id,
            category="promotion",
            title="优惠券即将过期",
            content=f"您在积分商城领取的优惠券即将于 {expires_local} 过期，请及时使用。",
            link="/promotion",
            level="warning",
        )
        db.add(NotificationEvent(
            event_key=event_key,
            event_type="coupon_expire_reminder",
            user_id=claim.user_id,
            resource_type="coupon_mall_claim",
            resource_id=claim.id,
        ))
        notified += 1

    if notified:
        await db.flush()
    return {
        "checked": len(rows),
        "notified": notified,
        "skipped": skipped,
    }
