"""POS 退货 API。

退货请求由 Agent 代理转发，因此用 Lane 同步令牌认证设备身份；操作人/审批人身份
由 Agent 侧的员工会话验证后随请求带上。Admin 查询走管理员权限。
"""
from decimal import Decimal

from fastapi import APIRouter, Depends, Header, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_db, get_admin_user, require_permission
from app.core.models.user import User
from app.plugins.pos_operations import returns_service, services
from app.plugins.pos_operations.returns import ReturnInvalid
from app.plugins.pos_operations.schemas import (
    ProductAgeIn, ReturnCreateIn, ReturnPlanIn, ReturnSettleIn,
)

returns_router = APIRouter(prefix="/pos/operations/returns", tags=["POS Returns"])
returns_admin_router = APIRouter(prefix="/pos/operations/admin", tags=["POS Operations Admin"])


async def _require_admin_enabled(db: AsyncSession, tenant_id: int) -> None:
    from app.core.services.plugin_helper import require_plugin
    await require_plugin("pos_operations", db, tenant_id)


_lane_from_token = services.lane_from_token


@returns_router.get("/order")
async def lookup_order(orderNo: str, authorization: str | None = Header(default=None),
                       db: AsyncSession = Depends(get_db)):
    lane = await _lane_from_token(db, authorization)
    return await returns_service.lookup_order_for_return(
        db, tenant_id=lane.tenant_id, store_id=lane.store_id, order_no=orderNo)


@returns_router.post("/plan")
async def plan_return(body: ReturnPlanIn, authorization: str | None = Header(default=None),
                      db: AsyncSession = Depends(get_db)):
    lane = await _lane_from_token(db, authorization)
    try:
        return await returns_service.plan_receipt_return(
            db, tenant_id=lane.tenant_id, store_id=lane.store_id, order_no=body.orderNo,
            requested=[i.model_dump() for i in body.items], refund_method=body.refundMethod,
            refund_amount_cents=body.refundAmountCents, money_only=body.moneyOnly)
    except ReturnInvalid as e:
        raise HTTPException(status_code=422, detail=str(e))


@returns_router.get("/reference-price")
async def get_reference_price(productId: int, variantId: int | None = None,
                              authorization: str | None = Header(default=None),
                              db: AsyncSession = Depends(get_db)):
    lane = await _lane_from_token(db, authorization)
    rules = await services.get_rules(db, tenant_id=lane.tenant_id, store_id=lane.store_id)
    try:
        return await returns_service.reference_price_cents(
            db, tenant_id=lane.tenant_id, store_id=lane.store_id, product_id=productId,
            variant_id=variantId, window_days=rules.return_window_days)
    except ReturnInvalid as e:
        raise HTTPException(status_code=422, detail=str(e))


async def _plan_receiptless(db: AsyncSession, lane, rules, body: ReturnCreateIn) -> dict:
    """无小票退货定价：参考价 = min(当前售价, 期限内最低成交价)；改价不得高于当前售价。"""
    items: list[dict] = []
    total = 0
    reference_total = 0
    for line in body.items:
        if line.productId is None:
            raise ReturnInvalid("无小票退货必须指定商品")
        pricing = await returns_service.reference_price_cents(
            db, tenant_id=lane.tenant_id, store_id=lane.store_id, product_id=line.productId,
            variant_id=line.variantId, window_days=rules.return_window_days)
        reference = pricing["referencePriceCents"]
        unit = line.unitPriceCents if line.unitPriceCents is not None else reference

        if unit < 0:
            raise ReturnInvalid("退货单价不能为负")
        if unit > pricing["currentPriceCents"]:
            raise ReturnInvalid("退货单价不得高于当前售价")
        if unit != reference and not (body.overrideReason or "").strip():
            raise ReturnInvalid("修改价格必须填写原因")

        qty = Decimal(str(line.quantity))
        if qty <= 0:
            raise ReturnInvalid("退货数量必须大于 0")
        amount = int((Decimal(unit) * qty).quantize(Decimal("1")))
        total += amount
        reference_total += int((Decimal(reference) * qty).quantize(Decimal("1")))
        items.append({
            "orderItemId": None, "productId": line.productId, "variantId": line.variantId,
            "name": line.name or "", "quantity": str(qty),
            "unitPriceCents": unit, "lineTotalCents": amount,
        })

    return {
        "ok": True, "orderId": None, "customerId": body.customerId,
        "rulesVersion": rules.rules_version, "items": items,
        "refundTotalCents": total, "splits": None,
        "referencePriceCents": reference_total, "overridePriceCents": total,
    }


def _return_out(record) -> dict:
    return {
        "ok": True, "returnId": record.id, "fundStatus": record.fund_status,
        "refundTotalCents": record.refund_total_cents, "refundMethod": record.refund_method,
        "splits": record.refund_splits or [],
    }


@returns_router.post("")
async def create_return(body: ReturnCreateIn, authorization: str | None = Header(default=None),
                        db: AsyncSession = Depends(get_db)):
    """落一条 pending 退货。资金尚未发生，POS 随后调用 /settle 上报结果。"""
    lane = await _lane_from_token(db, authorization)
    rules = await services.get_rules(db, tenant_id=lane.tenant_id, store_id=lane.store_id)

    if rules.all_returns_require_second_approval and not body.approverUserId:
        raise HTTPException(status_code=422, detail="退货必须由第二人批准")
    if body.approverUserId == body.operatorUserId:
        raise HTTPException(status_code=422, detail="审批人必须是另一名员工")

    sync_token = authorization.split(" ", 1)[1].strip() if authorization else ""
    if body.approverUserId and not services.verify_approval_evidence(
        body.approvalEvidence, sync_token=sync_token, action_type="return",
        tenant_id=lane.tenant_id, store_id=lane.store_id, lane_id=lane.lane_id,
        operator_user_id=body.operatorUserId, approver_user_id=body.approverUserId,
        content_hash=services.return_approval_content_hash(body),
        idempotency_key=body.idempotencyKey,
    ):
        raise HTTPException(status_code=403, detail="invalid or expired return approval evidence")

    # 服务端独立校验双方身份、启用状态、门店范围与权限——不能只信 Agent 传来的 ID。
    try:
        await services.verify_pos_actor(
            db, tenant_id=lane.tenant_id, store_id=lane.store_id,
            user_id=body.operatorUserId, permission="pos.return_create")
        if body.approverUserId:
            # 云端自己校验审批人 PIN——Agent 侧的校验只是客户端行为，不构成证据。
            await services.verify_pos_actor(
                db, tenant_id=lane.tenant_id, store_id=lane.store_id,
                user_id=body.approverUserId, permission="pos.return_approve")
    except services.PosActorInvalid as e:
        raise HTTPException(status_code=403, detail=str(e))

    # 同一幂等键 = 同一笔退货。重试（例如卡刷退款失败后再刷一次）必须在 plan 之前短路，
    # 否则第一次落库的 pending 记录会把自己的可退额度吃光，重算 plan 必然 422。
    existing = await returns_service.find_return_by_key(
        db, tenant_id=lane.tenant_id, idempotency_key=body.idempotencyKey)
    if existing is not None:
        return _return_out(existing)

    try:
        if body.sourceType == "receipt":
            if body.moneyOnly and body.refundAmountCents is None:
                raise ReturnInvalid("部分退款必须填写退款金额")
            plan = await returns_service.plan_receipt_return(
                db, tenant_id=lane.tenant_id, store_id=lane.store_id, order_no=body.orderNo or "",
                requested=[i.model_dump() for i in body.items], refund_method=body.refundMethod,
                refund_amount_cents=body.refundAmountCents, money_only=body.moneyOnly)
            if not plan.get("ok"):
                raise HTTPException(status_code=422, detail=plan.get("reason", "return_rejected"))
            refund_method = body.refundMethod
        else:
            if not rules.receiptless_returns_enabled:
                raise HTTPException(status_code=422, detail="本店未开放无小票退货")
            if body.customerId is None:
                raise HTTPException(status_code=422, detail="无小票退货必须绑定顾客")
            if await returns_service.resolve_customer(db, lane.tenant_id, body.customerId) is None:
                raise HTTPException(status_code=422, detail="顾客不存在")
            plan = await _plan_receiptless(db, lane, rules, body)
            refund_method = rules.receiptless_refund_method

        record = await returns_service.create_return(
            db, tenant_id=lane.tenant_id, store_id=lane.store_id, lane_id=lane.lane_id,
            idempotency_key=body.idempotencyKey, plan=plan,
            operator_user_id=body.operatorUserId, approver_user_id=body.approverUserId,
            reason=body.reason, stock_disposition="none" if body.moneyOnly else body.stockDisposition,
            source_type=body.sourceType, customer_id=body.customerId,
            reference_price_cents=plan.get("referencePriceCents"),
            override_price_cents=plan.get("overridePriceCents"),
            override_reason=body.overrideReason, refund_method=refund_method,
        )
    except ReturnInvalid as e:
        raise HTTPException(status_code=422, detail=str(e))

    return _return_out(record)


@returns_router.post("/{return_id}/settle")
async def settle(return_id: int, body: ReturnSettleIn,
                 authorization: str | None = Header(default=None),
                 db: AsyncSession = Depends(get_db)):
    """上报资金结果。全部成功才完成退货并回补库存 / 发放 Store Credit。"""
    lane = await _lane_from_token(db, authorization)
    try:
        result = await returns_service.settle_return(
            db, tenant_id=lane.tenant_id, store_id=lane.store_id, lane_id=lane.lane_id,
            return_id=return_id,
            splits_result=[s.model_dump() for s in body.splits])
    except ReturnInvalid as e:
        raise HTTPException(status_code=422, detail=str(e))

    # Store Credit 现在由 settle_return 在同一事务内发放，这里不再二次提交。
    return result


@returns_router.post("/device-audit")
async def device_audit_uplift(body: dict, authorization: str | None = Header(default=None),
                              db: AsyncSession = Depends(get_db)):
    """接收 Agent 上行的高风险设备审计（开柜），写入云端 audit_logs。

    Agent 侧 device_events 是纯本地的；开柜是现金接触点，总部必须能追溯，故单独上行。
    lane token 鉴权，服务端强制 tenant/store 取自 lane，Agent 无法伪造门店。
    """
    lane = await _lane_from_token(db, authorization)
    from app.services.audit import log_audit
    events = body.get("events") or []
    accepted = []
    for ev in events[:100]:
        payload = ev.get("payload") or {}
        await log_audit(
            db, tenant_id=lane.tenant_id, action=f"pos.drawer.{'auto' if payload.get('auto') else 'manual'}",
            actor_type="pos", actor_id=payload.get("operatorUserId"),
            target_type="cash_drawer", target_id=lane.store_id,
            target_name=ev.get("upliftKey"),
            changes={**payload, "laneId": lane.lane_id, "storeId": lane.store_id,
                     "agentAt": ev.get("createdAt")},
        )
        accepted.append(ev.get("upliftKey"))
    await db.commit()
    return {"ok": True, "accepted": accepted}


@returns_admin_router.get("/product-ages", dependencies=[Depends(require_permission("pos.rules.manage"))])
async def list_product_ages_endpoint(q: str = "", only_restricted: bool = False, limit: int = 50,
                                     db: AsyncSession = Depends(get_db),
                                     current_user: User = Depends(get_admin_user)):
    await _require_admin_enabled(db, current_user.tenant_id)
    return await returns_service.list_product_ages(
        db, tenant_id=current_user.tenant_id, q=q.strip(),
        only_restricted=only_restricted, limit=max(1, min(limit, 200)))


@returns_admin_router.put("/product-ages", dependencies=[Depends(require_permission("pos.rules.manage"))])
async def set_product_age_endpoint(body: ProductAgeIn, db: AsyncSession = Depends(get_db),
                                   current_user: User = Depends(get_admin_user)):
    """在插件内维护商品年龄限制，不改动共享的商品更新接口。"""
    try:
        await _require_admin_enabled(db, current_user.tenant_id)
        return await returns_service.set_product_age(
            db, tenant_id=current_user.tenant_id,
            product_id=body.product_id, minimum_age=body.minimum_age)
    except ReturnInvalid as e:
        raise HTTPException(status_code=422, detail=str(e))


@returns_admin_router.get("/returns", dependencies=[Depends(require_permission("pos.audit.view"))])
async def list_returns_endpoint(store_id: int | None = None, fund_status: str | None = None,
                                limit: int = 50, db: AsyncSession = Depends(get_db),
                                current_user: User = Depends(get_admin_user)):
    await _require_admin_enabled(db, current_user.tenant_id)
    return await returns_service.list_returns(
        db, tenant_id=current_user.tenant_id, store_id=store_id,
        fund_status=fund_status, limit=max(1, min(limit, 200)))
