from fastapi import APIRouter, Depends, Header, HTTPException
from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession

from app.database import get_db
from app.plugins.pos_sync.schemas import (
    PosMemberCreateIn, PosMemberOut, PosPromoQuoteIn, PosPromoQuoteOut, PosQuoteIn, PosQuoteOut, PosSyncOrderIn, PosSyncOrderOut,
    PosWalletChargeIn, PosWalletChargeOut,
)
from app.plugins.pos_sync.services import (
    authenticate_lane_by_ids, authenticate_lane_by_token, charge_member_balance, create_member,
    exchange_pairing_code, get_member_balance, get_receipt_detail, lookup_receipts, quote_cart, quote_promotions,
    search_members, sync_order,
)
from app.plugins.pos_sync.snapshot import build_snapshot_page, _parse_dt


router = APIRouter(prefix="/pos/sync", tags=["POS Sync"])


async def _require_operations_enabled(db: AsyncSession, tenant_id: int) -> None:
    from app.core.services.plugin_helper import require_plugin
    await require_plugin("pos_operations", db, tenant_id)


@router.post("/orders", response_model=PosSyncOrderOut)
async def sync_pos_order(
    body: PosSyncOrderIn,
    authorization: str | None = Header(default=None),
    db: AsyncSession = Depends(get_db),
):
    if not authorization or not authorization.lower().startswith("bearer "):
        raise HTTPException(status_code=401, detail="missing POS sync token")
    token = authorization.split(" ", 1)[1].strip()
    return await sync_order(db, body, token)


class PairingExchangeIn(BaseModel):
    pairingCode: str


pairing_router = APIRouter(prefix="/pos/pairing", tags=["POS Pairing"])


@pairing_router.post("/exchange")
async def pairing_exchange(body: PairingExchangeIn, db: AsyncSession = Depends(get_db)):
    return await exchange_pairing_code(db, body.pairingCode)


snapshot_router = APIRouter(prefix="/pos/snapshot", tags=["POS Snapshot"])


@snapshot_router.get("/products")
async def snapshot_products(
    tenantId: int,
    storeId: int,
    laneId: str,
    since: str | None = None,
    cursor: str | None = None,
    limit: int = 500,
    snapshotAt: str | None = None,
    expectedRevision: int | None = None,
    authorization: str | None = Header(default=None),
    db: AsyncSession = Depends(get_db),
):
    if not authorization or not authorization.lower().startswith("bearer "):
        raise HTTPException(status_code=401, detail="missing POS sync token")
    # Pagination contract: a continuation page (has cursor) MUST carry the run's fixed
    # snapshotAt upper bound and expectedRevision, otherwise the server would recompute a
    # new time window per page and rows could be skipped or duplicated mid-pull.
    if cursor and (snapshotAt is None or expectedRevision is None):
        raise HTTPException(status_code=400, detail="cursor requires snapshotAt and expectedRevision")
    token = authorization.split(" ", 1)[1].strip()
    lane = await authenticate_lane_by_ids(db, tenantId, storeId, laneId, token)
    snap = _parse_dt(snapshotAt) if snapshotAt else None
    return await build_snapshot_page(
        db, lane, since=since, cursor=cursor, limit=max(1, min(limit, 500)),
        snapshot_at=snap, expected_revision=expectedRevision,
    )


members_router = APIRouter(prefix="/pos/members", tags=["POS Members"])


@members_router.get("/search")
async def members_search(
    q: str,
    limit: int = 20,
    authorization: str | None = Header(default=None),
    db: AsyncSession = Depends(get_db),
):
    if not authorization or not authorization.lower().startswith("bearer "):
        raise HTTPException(status_code=401, detail="missing POS sync token")
    token = authorization.split(" ", 1)[1].strip()
    lane = await authenticate_lane_by_token(db, token)
    q = (q or "").strip()
    if not q:
        return {"members": []}
    return await search_members(db, lane.tenant_id, q, limit=max(1, min(limit, 50)))


@members_router.post("", response_model=PosMemberOut)
async def members_create(
    body: PosMemberCreateIn,
    authorization: str | None = Header(default=None),
    db: AsyncSession = Depends(get_db),
):
    """柜台建会员。手机号/邮箱已存在时返回既有会员，不报冲突。"""
    if not authorization or not authorization.lower().startswith("bearer "):
        raise HTTPException(status_code=401, detail="missing POS sync token")
    token = authorization.split(" ", 1)[1].strip()
    lane = await authenticate_lane_by_token(db, token)
    await _require_operations_enabled(db, lane.tenant_id)
    return await create_member(db, lane.tenant_id, body)


receipts_router = APIRouter(prefix="/pos/receipts", tags=["POS Receipts"])


@receipts_router.get("/lookup")
async def receipts_lookup(
    q: str = "",
    date: str = "",
    limit: int = 20,
    offset: int = 0,
    authorization: str | None = Header(default=None),
    db: AsyncSession = Depends(get_db),
):
    if not authorization or not authorization.lower().startswith("bearer "):
        raise HTTPException(status_code=401, detail="missing POS sync token")
    token = authorization.split(" ", 1)[1].strip()
    lane = await authenticate_lane_by_token(db, token)
    await _require_operations_enabled(db, lane.tenant_id)
    return await lookup_receipts(
        db, tenant_id=lane.tenant_id, store_id=lane.store_id, q=q, date=date, limit=limit, offset=offset,
    )


@receipts_router.get("/detail")
async def receipts_detail(
    localOrderNo: str,
    authorization: str | None = Header(default=None),
    db: AsyncSession = Depends(get_db),
):
    """单张小票明细。Agent 本机查不到时回落到这里，跨机订单也能看明细。"""
    if not authorization or not authorization.lower().startswith("bearer "):
        raise HTTPException(status_code=401, detail="missing POS sync token")
    token = authorization.split(" ", 1)[1].strip()
    lane = await authenticate_lane_by_token(db, token)
    await _require_operations_enabled(db, lane.tenant_id)
    detail = await get_receipt_detail(
        db, tenant_id=lane.tenant_id, store_id=lane.store_id, local_order_no=localOrderNo,
    )
    if detail is None:
        raise HTTPException(status_code=404, detail="receipt not found")
    return detail


@members_router.post("/quote", response_model=PosQuoteOut)
async def members_quote(
    body: PosQuoteIn,
    authorization: str | None = Header(default=None),
    db: AsyncSession = Depends(get_db),
):
    if not authorization or not authorization.lower().startswith("bearer "):
        raise HTTPException(status_code=401, detail="missing POS sync token")
    token = authorization.split(" ", 1)[1].strip()
    lane = await authenticate_lane_by_token(db, token)
    await _require_operations_enabled(db, lane.tenant_id)
    return await quote_cart(db, lane.tenant_id, body, store_id=lane.store_id)


promo_router = APIRouter(prefix="/pos/promotions", tags=["POS Promotions"])


@promo_router.post("/quote", response_model=PosPromoQuoteOut)
async def promotions_quote(
    body: PosPromoQuoteIn,
    authorization: str | None = Header(default=None),
    db: AsyncSession = Depends(get_db),
):
    if not authorization or not authorization.lower().startswith("bearer "):
        raise HTTPException(status_code=401, detail="missing POS sync token")
    lane = await authenticate_lane_by_token(db, authorization.split(" ", 1)[1].strip())
    await _require_operations_enabled(db, lane.tenant_id)
    return await quote_promotions(db, lane.tenant_id, body, store_id=lane.store_id)


conflict_router = APIRouter(prefix="/pos/sync/conflicts", tags=["POS Sync"])


class CloseConflictIn(BaseModel):
    localOrderNo: str
    reason: str = Field(min_length=1, max_length=500)
    approverUserId: int


@conflict_router.post("/close")
async def close_conflict(
    body: CloseConflictIn,
    authorization: str | None = Header(default=None),
    db: AsyncSession = Depends(get_db),
):
    if not authorization or not authorization.lower().startswith("bearer "):
        raise HTTPException(status_code=401, detail="missing POS sync token")
    token = authorization.split(" ", 1)[1].strip()
    lane = await authenticate_lane_by_token(db, token)
    from app.plugins.pos_sync.services import close_conflict_record
    return await close_conflict_record(db, lane.tenant_id, lane.lane_id, body.localOrderNo, body.reason.strip(), body.approverUserId)


wallet_router = APIRouter(prefix="/pos/wallet", tags=["POS Wallet"])


@wallet_router.get("/members/{customer_id}/balance")
async def members_balance(
    customer_id: int,
    authorization: str | None = Header(default=None),
    db: AsyncSession = Depends(get_db),
):
    if not authorization or not authorization.lower().startswith("bearer "):
        raise HTTPException(status_code=401, detail="missing POS sync token")
    token = authorization.split(" ", 1)[1].strip()
    lane = await authenticate_lane_by_token(db, token)
    await _require_operations_enabled(db, lane.tenant_id)
    return await get_member_balance(db, lane.tenant_id, customer_id)


@wallet_router.post("/charge", response_model=PosWalletChargeOut)
async def wallet_charge(
    body: PosWalletChargeIn,
    authorization: str | None = Header(default=None),
    db: AsyncSession = Depends(get_db),
):
    if not authorization or not authorization.lower().startswith("bearer "):
        raise HTTPException(status_code=401, detail="missing POS sync token")
    token = authorization.split(" ", 1)[1].strip()
    lane = await authenticate_lane_by_token(db, token)
    await _require_operations_enabled(db, lane.tenant_id)
    return await charge_member_balance(db, lane.tenant_id, body)
