import secrets
from datetime import datetime, timedelta, timezone

from fastapi import HTTPException
from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession

from app.models import (
    AgentCustomer,
    AgentSettlement,
    AgentTier,
    Order,
    OrderStatus,
    PromoCode,
    User,
    UserAgent,
    UserInviteCode,
)
from app.models.user_agent import AgentStatus
from app.services import agent_balance_service
from app.services.agent_lifecycle_service import agent_settlement_enabled


DEFAULT_SETTLEMENT_FREEZE_DAYS = 3
INVITE_CODE_CHANGE_LIMIT = 3


def _generate_invite_code() -> str:
    return "QC" + secrets.token_hex(4).upper()


def normalize_invite_code(code: str | None) -> str:
    return str(code or "").strip().upper()


def invite_code_change_limit(user: User) -> int:
    return INVITE_CODE_CHANGE_LIMIT + max(0, int(getattr(user, "invite_code_change_extra", 0) or 0))


def invite_code_change_remaining(user: User) -> int:
    return max(0, invite_code_change_limit(user) - int(getattr(user, "invite_code_change_count", 0) or 0))


async def get_or_create_user_agent(db: AsyncSession, user_id: int) -> UserAgent:
    result = await db.execute(select(UserAgent).where(UserAgent.user_id == user_id))
    agent = result.scalar_one_or_none()
    if not agent:
        agent = UserAgent(user_id=user_id, status=AgentStatus.PENDING)
        db.add(agent)
        await db.flush()
    return agent


async def get_user_agent(db: AsyncSession, user_id: int) -> UserAgent | None:
    result = await db.execute(select(UserAgent).where(UserAgent.user_id == user_id))
    return result.scalar_one_or_none()


async def generate_invite_code(db: AsyncSession, user: User) -> str:
    if user.invite_code:
        await ensure_invite_code_record(db, user, user.invite_code, source="legacy", is_current=True)
        return user.invite_code
    for _ in range(5):
        code = "QCAFF" + secrets.token_hex(5).upper()
        existing_user = await db.execute(select(User).where(User.invite_code == code))
        existing_code = await db.execute(select(UserInviteCode).where(UserInviteCode.code == code))
        if not existing_user.scalar_one_or_none() and not existing_code.scalar_one_or_none():
            user.invite_code = code
            db.add(UserInviteCode(user_id=user.id, code=code, is_current=True, source="generated"))
            await db.flush()
            return code
    raise RuntimeError("Failed to generate unique invite code")


async def ensure_invite_code_record(
    db: AsyncSession,
    user: User,
    code: str | None,
    *,
    source: str,
    is_current: bool,
) -> UserInviteCode | None:
    normalized = normalize_invite_code(code)
    if not normalized:
        return None
    result = await db.execute(select(UserInviteCode).where(UserInviteCode.code == normalized))
    record = result.scalar_one_or_none()
    if record:
        if record.user_id != user.id:
            raise HTTPException(status_code=400, detail="该邀请码已被占用")
        if is_current:
            await db.execute(
                update(UserInviteCode).where(UserInviteCode.user_id == user.id).values(is_current=False)
            )
            record.is_current = True
        return record
    record = UserInviteCode(user_id=user.id, code=normalized, is_current=is_current, source=source)
    if is_current:
        await db.execute(update(UserInviteCode).where(UserInviteCode.user_id == user.id).values(is_current=False))
    db.add(record)
    await db.flush()
    return record


async def resolve_agent_from_code(db: AsyncSession, code: str) -> User | None:
    normalized = normalize_invite_code(code)
    if not normalized:
        return None
    result = await db.execute(select(User).where(User.invite_code == normalized))
    user = result.scalar_one_or_none()
    if user:
        return user
    record_result = await db.execute(select(UserInviteCode).where(UserInviteCode.code == normalized))
    record = record_result.scalar_one_or_none()
    if record:
        return await db.get(User, record.user_id)
    agent_result = await db.execute(select(UserAgent).where(UserAgent.invite_code == normalized))
    agent = agent_result.scalar_one_or_none()
    if agent:
        return await db.get(User, agent.user_id)
    return None


async def change_user_invite_code(db: AsyncSession, user: User, code: str) -> tuple[str, int, int]:
    normalized = normalize_invite_code(code)
    if not normalized:
        raise HTTPException(status_code=400, detail="邀请码不能为空")
    if normalized == normalize_invite_code(user.invite_code):
        await ensure_invite_code_record(db, user, normalized, source="manual", is_current=True)
        return normalized, int(user.invite_code_change_count or 0), invite_code_change_limit(user)

    change_count = int(user.invite_code_change_count or 0)
    change_limit = invite_code_change_limit(user)
    if change_count >= change_limit:
        raise HTTPException(status_code=400, detail=f"邀请码最多可修改 {change_limit} 次")

    owner = await resolve_agent_from_code(db, normalized)
    if owner and owner.id != user.id:
        raise HTTPException(status_code=400, detail="该邀请码已被占用")

    await ensure_invite_code_record(db, user, user.invite_code, source="legacy", is_current=False)
    await ensure_invite_code_record(db, user, normalized, source="manual", is_current=True)
    user.invite_code = normalized
    user.invite_code_change_count = change_count + 1
    await db.flush()
    return normalized, user.invite_code_change_count, change_limit


async def bind_customer_to_agent(
    db: AsyncSession,
    agent_user_id: int,
    customer_user_id: int,
    bind_source: str = "invite_code",
) -> AgentCustomer:
    existing = await db.execute(
        select(AgentCustomer).where(AgentCustomer.customer_user_id == customer_user_id)
    )
    binding = existing.scalar_one_or_none()
    if binding:
        return binding

    await db.execute(
        update(User).where(User.id == customer_user_id).values(bound_agent_id=agent_user_id)
    )

    binding = AgentCustomer(
        agent_user_id=agent_user_id,
        customer_user_id=customer_user_id,
        bind_source=bind_source,
        bound_at=datetime.now(timezone.utc),
    )
    db.add(binding)
    await db.flush()
    return binding


async def get_agent_customers(
    db: AsyncSession,
    agent_user_id: int,
    offset: int = 0,
    limit: int = 20,
):
    query = (
        select(AgentCustomer, User)
        .join(User, User.id == AgentCustomer.customer_user_id)
        .where(AgentCustomer.agent_user_id == agent_user_id)
        .order_by(AgentCustomer.bound_at.desc())
        .offset(offset)
        .limit(limit)
    )
    result = await db.execute(query)
    return result.all()


async def get_customer_count(db: AsyncSession, agent_user_id: int) -> int:
    result = await db.execute(
        select(AgentCustomer).where(AgentCustomer.agent_user_id == agent_user_id)
    )
    return len(result.scalars().all())


async def get_agent_settlements(
    db: AsyncSession,
    user_id: int,
    offset: int = 0,
    limit: int = 20,
):
    query = (
        select(AgentSettlement)
        .where(AgentSettlement.user_id == user_id)
        .order_by(AgentSettlement.id.desc())
        .offset(offset)
        .limit(limit)
    )
    result = await db.execute(query)
    return result.scalars().all()


async def get_agent_pending_profit(db: AsyncSession, agent_user_id: int) -> int:
    result = await db.execute(
        select(AgentSettlement).where(
            AgentSettlement.user_id == agent_user_id,
            AgentSettlement.settlement_type == "agent_profit",
            AgentSettlement.status.in_(["pending", "frozen"]),
        )
    )
    total = 0
    for settlement in result.scalars().all():
        if settlement.delta_amount > 0:
            total += settlement.delta_amount
    return total


async def calculate_order_settlement(
    db: AsyncSession,
    order: Order,
    customer_id: int,
    customer_pay_amount: int | None = None,
):
    settlements: list[AgentSettlement] = []

    user_result = await db.execute(select(User).where(User.id == customer_id))
    customer = user_result.scalar_one_or_none()
    if not customer:
        return settlements

    agent = None
    agent_tier = None
    if customer.bound_agent_id:
        agent_result = await db.execute(
            select(UserAgent).where(
                UserAgent.user_id == customer.bound_agent_id,
                UserAgent.status.in_([AgentStatus.ACTIVE, AgentStatus.OBSERVING]),
            )
        )
        agent = agent_result.scalar_one_or_none()
        if agent and not agent_settlement_enabled(agent.status):
            agent = None
        if agent and agent.agent_tier_id:
            tier_result = await db.execute(
                select(AgentTier).where(AgentTier.id == agent.agent_tier_id)
            )
            agent_tier = tier_result.scalar_one_or_none()

    standard_amount = order.total_amount
    customer_pay = standard_amount if customer_pay_amount is None else customer_pay_amount

    if agent and agent_tier and agent_tier.cost_rate and agent.user_id != customer_id:
        cost_rate = agent_tier.cost_rate
        cost_amount = int(standard_amount * cost_rate / 10000)
        delta = customer_pay - cost_amount

        order.agent_id = agent.user_id
        order.agent_tier_id = agent_tier.id
        order.agent_cost_rate = cost_rate
        order.agent_cost_amount = cost_amount
        order.agent_delta_amount = delta

        settlement = AgentSettlement(
            order_id=order.id,
            order_no=order.out_order_no,
            settlement_type="agent_profit" if delta >= 0 else "agent_subsidy",
            user_id=agent.user_id,
            customer_user_id=customer_id,
            agent_tier_id=agent_tier.id,
            standard_amount=standard_amount,
            customer_pay_amount=customer_pay,
            agent_cost_rate=cost_rate,
            agent_cost_amount=cost_amount,
            delta_amount=delta,
            points_amount=max(0, delta * 10),
            status="pending",
        )
        settlements.append(settlement)

        agent.total_sales += standard_amount
        if delta > 0:
            agent.total_profit += delta
        elif delta < 0:
            agent.total_subsidy += abs(delta)

    order.settlement_status = "pending" if settlements else None
    return settlements


async def freeze_agent_subsidy_for_order(
    db: AsyncSession,
    order: Order,
) -> None:
    """如果订单存在代理补贴，下单时冻结代理余额。"""
    if not order.agent_id or (order.agent_delta_amount or 0) >= 0:
        return
    subsidy = abs(order.agent_delta_amount or 0)
    await agent_balance_service.freeze_subsidy(
        db,
        agent_user_id=order.agent_id,
        amount=subsidy,
        order_id=order.id,
        ref_no=order.out_order_no,
        remark=f"订单 {order.out_order_no} 冻结代理补贴 {subsidy} 分",
    )


def _settlement_release_at(order: Order, freeze_days: int) -> datetime | None:
    created_at = order.created_at or datetime.now(timezone.utc)
    if created_at.tzinfo is None:
        created_at = created_at.replace(tzinfo=timezone.utc)
    return created_at + timedelta(days=max(0, freeze_days))


async def settle_order(db: AsyncSession, order: Order, *, force: bool = False) -> int:
    """Freeze rewards during the refund window, then credit points / agent balance."""
    from app.models.point_log import PointChangeType
    from app.services import membership_service as _msvc
    from app.services import system_setting_service as _settings_svc

    now = datetime.now(timezone.utc)
    try:
        settings = await _settings_svc.get_business_settings(db)
        freeze_days = settings.full_refund_days
    except Exception:
        freeze_days = DEFAULT_SETTLEMENT_FREEZE_DAYS
    release_at = _settlement_release_at(order, freeze_days)
    should_freeze = bool(release_at and now < release_at and not force)

    result = await db.execute(
        select(AgentSettlement).where(
            AgentSettlement.order_id == order.id,
            AgentSettlement.status.in_(["pending", "frozen"]),
        )
    )
    settlements = list(result.scalars().all())

    for settlement in settlements:
        points = max(0, settlement.delta_amount * 10)
        settlement.points_amount = points

        if should_freeze:
            settlement.status = "frozen"
            settlement.frozen_until = release_at
            settlement.remark = "全额退订窗口内冻结，窗口结束后自动转可用积分"
            continue

        if settlement.settlement_type == "agent_subsidy":
            # 补贴订单：将冻结的补贴转为实际扣除
            subsidy = abs(settlement.delta_amount)
            await agent_balance_service.charge_subsidy(
                db,
                agent_user_id=settlement.user_id,
                amount=subsidy,
                order_id=order.id,
                ref_no=order.out_order_no,
                remark=f"订单 {order.out_order_no} 扣除代理补贴 {subsidy} 分",
            )
            settlement.status = "settled"
            settlement.settled_at = now
            settlement.remark = f"已扣除代理补贴 {subsidy} 分"
            continue

        if settlement.delta_amount <= 0:
            settlement.status = "settled"
            settlement.settled_at = now
            settlement.remark = "非正向收益，无积分发放"
            continue

        if settlement.point_log_id:
            settlement.status = "settled"
            settlement.settled_at = settlement.settled_at or now
            continue

        if settlement.settlement_type == "agent_profit":
            # 代理收益同时入账代理余额和积分
            await agent_balance_service.credit_profit(
                db,
                agent_user_id=settlement.user_id,
                amount=settlement.delta_amount,
                order_id=order.id,
                ref_no=order.out_order_no,
                remark=f"订单 {order.out_order_no} 结算代理收益 {settlement.delta_amount} 分",
            )
            point_log = await _msvc.add_points(
                db=db,
                user_id=settlement.user_id,
                delta=points,
                change_type=PointChangeType.AGENT_PROFIT,
                order_id=order.id,
                ref_no=order.out_order_no,
                remark=(
                    f"agent_profit order {settlement.order_no} "
                    f"settled: {settlement.delta_amount} cents -> {points} points"
                ),
            )
            settlement.point_log_id = point_log.id
        elif settlement.settlement_type == "member_commission":
            point_log = await _msvc.add_points(
                db=db,
                user_id=settlement.user_id,
                delta=points,
                change_type=PointChangeType.REFERRAL_REWARD,
                order_id=order.id,
                ref_no=order.out_order_no,
                remark=(
                    f"member_commission order {settlement.order_no} "
                    f"settled: {settlement.delta_amount} cents -> {points} points"
                ),
            )
            settlement.point_log_id = point_log.id
        else:
            point_log = await _msvc.add_points(
                db=db,
                user_id=settlement.user_id,
                delta=points,
                change_type=PointChangeType.ACTIVITY_GIFT,
                order_id=order.id,
                ref_no=order.out_order_no,
                remark=(
                    f"{settlement.settlement_type} order {settlement.order_no} "
                    f"settled: {settlement.delta_amount} cents -> {points} points"
                ),
            )
            settlement.point_log_id = point_log.id

        settlement.status = "settled"
        settlement.settled_at = now
        settlement.remark = settlement.remark or f"已发放 {points} 积分"

        if settlement.settlement_type == "member_commission":
            from app.services import notification_service

            await notification_service.safe_notify(
                db,
                user_id=settlement.user_id,
                category="promotion",
                title="推广奖励已到账",
                content=f"您的推广奖励 {points} 积分已发放到账，可在推广中心查看。",
                link="/promotion",
            )

    if settlements:
        order.settlement_status = "frozen" if should_freeze else "settled"
        await db.flush()
    return len(settlements)


async def settle_due_rewards_for_user(db: AsyncSession, user_id: int) -> int:
    now = datetime.now(timezone.utc)
    result = await db.execute(
        select(Order)
        .join(AgentSettlement, AgentSettlement.order_id == Order.id)
        .where(
            AgentSettlement.user_id == user_id,
            AgentSettlement.status == "frozen",
            AgentSettlement.frozen_until.is_not(None),
            AgentSettlement.frozen_until <= now,
            Order.status == OrderStatus.PAID,
        )
        .distinct()
    )
    orders = list(result.scalars().all())
    settled = 0
    for order in orders:
        settled += await settle_order(db, order, force=True)
    return settled


async def settle_due_rewards(db: AsyncSession, *, limit: int = 100) -> dict[str, int]:
    now = datetime.now(timezone.utc)
    result = await db.execute(
        select(Order)
        .join(AgentSettlement, AgentSettlement.order_id == Order.id)
        .where(
            AgentSettlement.status == "frozen",
            AgentSettlement.frozen_until.is_not(None),
            AgentSettlement.frozen_until <= now,
            Order.status == OrderStatus.PAID,
        )
        .order_by(AgentSettlement.frozen_until.asc(), Order.id.asc())
        .distinct()
        .limit(limit)
    )
    orders = list(result.scalars().all())
    settled = 0
    for order in orders:
        settled += await settle_order(db, order, force=True)
    return {"orders": len(orders), "settlements": settled}


async def reverse_order_settlements(db: AsyncSession, order: Order, *, reason: str) -> int:
    from app.models.point_log import PointChangeType, PointLog
    from app.services import membership_service as _msvc

    result = await db.execute(
        select(AgentSettlement).where(
            AgentSettlement.order_id == order.id,
            AgentSettlement.status.in_(["pending", "frozen", "settled"]),
        )
    )
    settlements = list(result.scalars().all())
    now = datetime.now(timezone.utc)
    for settlement in settlements:
        if settlement.settlement_type == "agent_subsidy":
            subsidy = abs(settlement.delta_amount)
            if settlement.status == "pending":
                # 补贴仍在冻结中，直接释放
                await agent_balance_service.release_subsidy(
                    db,
                    agent_user_id=settlement.user_id,
                    amount=subsidy,
                    order_id=order.id,
                    ref_no=order.out_order_no,
                    remark=f"订单退款释放代理补贴：{reason[:120]}",
                )
            else:
                # 补贴已扣除，返还代理余额
                await agent_balance_service.refund_subsidy(
                    db,
                    agent_user_id=settlement.user_id,
                    amount=subsidy,
                    order_id=order.id,
                    ref_no=order.out_order_no,
                    remark=f"订单退款返还代理补贴：{reason[:120]}",
                )
        elif settlement.settlement_type == "agent_profit" and settlement.delta_amount > 0:
            # 收益已入账余额，退款时扣回
            await agent_balance_service.reverse_profit(
                db,
                agent_user_id=settlement.user_id,
                amount=settlement.delta_amount,
                order_id=order.id,
                ref_no=order.out_order_no,
                remark=f"订单退款扣回代理收益：{reason[:120]}",
            )
        elif settlement.point_log_id and settlement.points_amount > 0:
            reverse_ref = f"{order.out_order_no}:agent_reverse:{settlement.id}"
            existing = await db.execute(
                select(PointLog).where(
                    PointLog.ref_no == reverse_ref,
                    PointLog.change_type == PointChangeType.ORDER_REFUND,
                )
            )
            if not existing.scalar_one_or_none():
                await _msvc.add_points(
                    db=db,
                    user_id=settlement.user_id,
                    delta=-settlement.points_amount,
                    change_type=PointChangeType.ORDER_REFUND,
                    order_id=order.id,
                    ref_no=reverse_ref,
                    remark=f"代理收益退订扣回：{reason[:120]}",
                    allow_negative=True,
                )
        settlement.status = "reversed"
        settlement.reversed_at = now
        settlement.remark = reason
    if settlements:
        order.settlement_status = "reversed"
        await db.flush()
    return len(settlements)
