from dataclasses import dataclass
from datetime import datetime

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

from app.models.member_level import MemberLevel
from app.models.spend_event import SpendEventTypeConfig, UserSpendEvent
from app.services import membership_service


class SpendEventType:
    SERVER_PURCHASE = "server_purchase"
    SERVER_RENEWAL = "server_renewal"
    PACKAGE_UPGRADE = "package_upgrade"
    DISK_CREATE = "disk_create"
    DISK_RESIZE = "disk_resize"
    DISK_RENEWAL = "disk_renewal"
    BANDWIDTH_UPGRADE = "bandwidth_upgrade"
    BACKUP_ADDON = "backup_addon"


@dataclass(frozen=True)
class DefaultSpendEventType:
    code: str
    label: str
    description: str
    enabled: bool
    sort_order: int


DEFAULT_SPEND_EVENT_TYPES = [
    DefaultSpendEventType(
        SpendEventType.SERVER_PURCHASE,
        "购买云服务器",
        "用户购买云服务器产生的实付产品消费",
        True,
        10,
    ),
    DefaultSpendEventType(
        SpendEventType.SERVER_RENEWAL,
        "云服务器续费",
        "云服务器续费中的实例基础续费金额",
        True,
        20,
    ),
    DefaultSpendEventType(
        SpendEventType.PACKAGE_UPGRADE,
        "套餐升级/配置升级",
        "切换到更高套餐或配置升级产生的补差金额",
        True,
        30,
    ),
    DefaultSpendEventType(
        SpendEventType.DISK_CREATE,
        "添加弹性云盘",
        "新增数据盘产生的本期扣费",
        True,
        40,
    ),
    DefaultSpendEventType(
        SpendEventType.DISK_RESIZE,
        "磁盘扩容",
        "数据盘扩容产生的本期扣费",
        True,
        50,
    ),
    DefaultSpendEventType(
        SpendEventType.DISK_RENEWAL,
        "磁盘续费",
        "云服务器续费时附带的数据盘续费金额",
        True,
        60,
    ),
    DefaultSpendEventType(
        SpendEventType.BANDWIDTH_UPGRADE,
        "带宽升级",
        "独立带宽升级产生的扣费，预留",
        True,
        70,
    ),
    DefaultSpendEventType(
        SpendEventType.BACKUP_ADDON,
        "备份/快照包",
        "备份、快照等增值服务消费，预留",
        True,
        80,
    ),
]


async def ensure_spend_event_type_configs(db: AsyncSession) -> list[SpendEventTypeConfig]:
    result = await db.execute(select(SpendEventTypeConfig))
    configs = {config.code: config for config in result.scalars().all()}

    for item in DEFAULT_SPEND_EVENT_TYPES:
        if item.code not in configs:
            config = SpendEventTypeConfig(
                code=item.code,
                label=item.label,
                description=item.description,
                enabled=item.enabled,
                sort_order=item.sort_order,
            )
            db.add(config)
            configs[item.code] = config

    await db.flush()
    return sorted(configs.values(), key=lambda config: (config.sort_order, config.id or 0))


async def get_spend_event_type_configs(db: AsyncSession) -> list[SpendEventTypeConfig]:
    await ensure_spend_event_type_configs(db)
    result = await db.execute(
        select(SpendEventTypeConfig).order_by(SpendEventTypeConfig.sort_order, SpendEventTypeConfig.id)
    )
    return list(result.scalars().all())


async def set_spend_event_type_enabled(
    db: AsyncSession,
    code: str,
    enabled: bool,
) -> tuple[SpendEventTypeConfig | None, bool]:
    await ensure_spend_event_type_configs(db)
    result = await db.execute(select(SpendEventTypeConfig).where(SpendEventTypeConfig.code == code))
    config = result.scalar_one_or_none()
    if not config:
        return None, False

    changed = config.enabled != enabled
    config.enabled = enabled
    await db.flush()
    return config, changed


async def record_spend_event(
    db: AsyncSession,
    *,
    user_id: int,
    event_type: str,
    amount_cents: int,
    event_key: str,
    order_id: int | None = None,
    ref_no: str | None = None,
    remark: str | None = None,
    payload_snapshot: dict | None = None,
) -> UserSpendEvent | None:
    if amount_cents == 0:
        return None

    await ensure_spend_event_type_configs(db)

    existing_result = await db.execute(
        select(UserSpendEvent).where(UserSpendEvent.event_key == event_key)
    )
    existing = existing_result.scalar_one_or_none()
    if existing:
        return existing

    event = UserSpendEvent(
        user_id=user_id,
        event_type=event_type,
        event_key=event_key,
        amount_cents=amount_cents,
        order_id=order_id,
        ref_no=ref_no,
        remark=remark,
        payload_snapshot=payload_snapshot,
    )
    db.add(event)
    await db.flush()
    await recalculate_user_total_spent(db, user_id)
    return event


async def reverse_spend_events_for_order(
    db: AsyncSession,
    *,
    user_id: int,
    order_id: int,
    refund_no: str,
    reason: str,
    ratio_bps: int = 10000,
) -> int:
    ratio_bps = max(0, min(10000, int(ratio_bps or 0)))
    if ratio_bps <= 0:
        return 0

    result = await db.execute(
        select(UserSpendEvent).where(
            UserSpendEvent.user_id == user_id,
            UserSpendEvent.order_id == order_id,
            UserSpendEvent.amount_cents > 0,
        )
    )
    events = list(result.scalars().all())
    reversed_amount = 0
    for event in events:
        reverse_amount = (int(event.amount_cents or 0) * ratio_bps) // 10000
        if reverse_amount <= 0:
            continue
        reverse_event = await record_spend_event(
            db,
            user_id=user_id,
            event_type=event.event_type,
            amount_cents=-reverse_amount,
            event_key=f"{event.event_key}:refund:{refund_no}",
            order_id=order_id,
            ref_no=refund_no,
            remark=reason,
            payload_snapshot={
                "refund_no": refund_no,
                "source_event_id": event.id,
                "source_event_key": event.event_key,
                "ratio_bps": ratio_bps,
            },
        )
        if reverse_event:
            reversed_amount += reverse_amount

    if events:
        await recalculate_user_total_spent(db, user_id)
    return reversed_amount


async def recalculate_user_total_spent(db: AsyncSession, user_id: int):
    membership = await membership_service.get_or_create_membership(db, user_id)

    result = await db.execute(
        select(func.coalesce(func.sum(UserSpendEvent.amount_cents), 0))
        .join(
            SpendEventTypeConfig,
            SpendEventTypeConfig.code == UserSpendEvent.event_type,
        )
        .where(
            UserSpendEvent.user_id == user_id,
            SpendEventTypeConfig.enabled.is_(True),
        )
    )
    total_spent = int(result.scalar_one() or 0)
    membership.total_spent = max(0, total_spent)

    quarter_spent = await membership_service.get_current_quarter_self_spent(db, user_id)

    if not membership.member_level_locked:
        result = await db.execute(
            select(MemberLevel)
            .where(
                MemberLevel.enabled.is_(True),
                MemberLevel.min_total_spent <= quarter_spent,
            )
            .order_by(MemberLevel.level.desc())
        )
        new_level = result.scalars().first()
        if new_level and membership.member_level_id != new_level.id:
            membership.member_level_id = new_level.id
            membership.upgraded_at = datetime.now()

    await db.flush()
    await db.refresh(membership)
    return membership


async def recalculate_all_membership_spent(db: AsyncSession) -> int:
    result = await db.execute(select(UserSpendEvent.user_id).distinct())
    user_ids = [int(user_id) for user_id in result.scalars().all()]
    for user_id in user_ids:
        await recalculate_user_total_spent(db, user_id)
    return len(user_ids)
