"""Admin / internal management API for the B2B ordering portal.

Gated by :func:`require_ordering_admin_session` (the Lean admin, or an internal
user with ``manage`` on the ordering module). Operates across all customers
within the seller's ordering tenant.
"""
from __future__ import annotations

import re
from datetime import datetime

from fastapi import APIRouter, Depends, HTTPException, Query, Response, status
from sqlalchemy import func, select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session, selectinload

from app.api.deps import AuthSession, require_ordering_admin_session
from app.db.session import get_db
from app.models.client_access import ClientAccount, ClientUser
from app.models.ordering import (
    CatalogueProduct,
    CustomerPriceAssignment,
    CustomerProductPrice,
    CustomerProductVisibility,
    Order,
    OrderLine,
    PriceList,
    PriceListItem,
    PriceTier,
    ProductCategory,
    XeroContactLink,
    XeroSyncLog,
)
from app.schemas.ordering import (
    CatalogueProductCreate,
    CatalogueProductUpdate,
    CategoryCreate,
    CategoryUpdate,
    CustomerCreate,
    CustomerPriceAssignmentUpsert,
    CustomerProductPriceUpsert,
    CustomerUpdate,
    CustomerUserCreate,
    CustomerUserUpdate,
    NotificationSettingsUpdate,
    OrderLineOverride,
    OrderStatusUpdate,
    PriceListCreate,
    PriceListItemUpsert,
    ReopenOrderRequest,
    VisibilityUpdate,
    XeroContactLinkUpsert,
)
from app.services import ordering_service as svc
from app.services.client_access_service import (
    ensure_user_module_permissions,
    record_audit_event,
)
from app.services.order_notifications import get_or_create_settings
from app.services.xero_service import (
    list_xero_contacts,
    submit_order_to_xero,
    xero_status_snapshot,
)

router = APIRouter(prefix="/api/ordering-admin", tags=["ordering-admin"])

TENANT = svc.ORDERING_TENANT


# --- Helpers -----------------------------------------------------------------


def _slugify(value: str) -> str:
    slug = re.sub(r"[^a-z0-9]+", "-", value.strip().lower()).strip("-")
    return slug or "item"


def _actor(session: AuthSession) -> dict[str, str]:
    return {
        "actor_type": "lean_admin",
        "actor_name": session.name or "Admin",
        "actor_email": session.email or "",
        "actor_role": session.client_role or session.role,
    }


def _customer_or_404(db: Session, customer_id: int) -> ClientAccount:
    account = db.scalar(select(ClientAccount).where(ClientAccount.id == customer_id))
    if account is None:
        raise HTTPException(status_code=404, detail="Customer not found")
    return account


def _product_or_404(db: Session, product_id: int) -> CatalogueProduct:
    product = db.scalar(
        select(CatalogueProduct).where(
            CatalogueProduct.id == product_id, CatalogueProduct.tenant_id == TENANT
        )
    )
    if product is None:
        raise HTTPException(status_code=404, detail="Product not found")
    return product


def _load_order(db: Session, order_id: int) -> Order:
    order = db.scalar(
        select(Order)
        .where(Order.id == order_id, Order.tenant_id == TENANT)
        .options(selectinload(Order.lines), selectinload(Order.status_history))
    )
    if order is None:
        raise HTTPException(status_code=404, detail="Order not found")
    return order


def _serialize_tiers(db: Session, *, customer_product_price_id=None, price_list_item_id=None) -> list[dict]:
    stmt = select(PriceTier)
    if customer_product_price_id is not None:
        stmt = stmt.where(PriceTier.customer_product_price_id == customer_product_price_id)
    else:
        stmt = stmt.where(PriceTier.price_list_item_id == price_list_item_id)
    return [
        {"id": t.id, "min_quantity": t.min_quantity, "unit_price": t.unit_price}
        for t in db.scalars(stmt.order_by(PriceTier.min_quantity)).all()
    ]


# --- Customers ---------------------------------------------------------------


def _xero_link_for(db: Session, customer_id: int) -> XeroContactLink | None:
    return db.scalar(
        select(XeroContactLink).where(
            XeroContactLink.tenant_id == TENANT,
            XeroContactLink.client_account_id == customer_id,
        )
    )


def _serialize_customer(db: Session, account: ClientAccount) -> dict:
    users = db.scalars(select(ClientUser).where(ClientUser.client_account_id == account.id)).all()
    assignment = db.scalar(
        select(CustomerPriceAssignment).where(CustomerPriceAssignment.client_account_id == account.id)
    )
    link = _xero_link_for(db, account.id)
    return {
        "id": account.id,
        "name": account.name,
        "client_code": account.client_code,
        "tenant_id": account.tenant_id,
        "status": account.status,
        "notes": account.notes,
        "user_count": len(users),
        "price_list_id": assignment.price_list_id if assignment else None,
        "discount_percent": assignment.discount_percent if assignment else 0.0,
        "xero_contact_id": link.xero_contact_id if link else None,
        "xero_contact_name": link.xero_contact_name if link else None,
        "created_at": account.created_at,
    }


@router.get("/customers")
def list_customers(
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    accounts = db.scalars(select(ClientAccount).order_by(ClientAccount.name)).all()
    return [_serialize_customer(db, a) for a in accounts]


@router.post("/customers", status_code=status.HTTP_201_CREATED)
def create_customer(
    payload: CustomerCreate,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    tenant_id = (payload.tenant_id or _slugify(payload.client_code)).strip() or _slugify(payload.name)
    account = ClientAccount(
        tenant_id=tenant_id,
        name=payload.name,
        client_code=payload.client_code,
        status="active",
        notes=payload.notes,
    )
    db.add(account)
    try:
        db.flush()
    except IntegrityError as exc:
        db.rollback()
        raise HTTPException(status_code=409, detail="A customer with that name or code already exists") from exc

    # Enable the ordering feature for the new customer so its users can access
    # the portal once granted module permissions.
    from app.models.client_access import ClientFeatureAccess
    from app.services.client_access_service import MODULE_INDEX

    info = MODULE_INDEX["ordering"]
    db.add(
        ClientFeatureAccess(
            tenant_id=account.tenant_id,
            client_account_id=account.id,
            feature_key="ordering",
            feature_name=info["module_name"],
            feature_group=info["module_group"],
            description=info["description"],
            enabled=True,
        )
    )
    record_audit_event(
        db,
        tenant_id=account.tenant_id,
        client_account_id=account.id,
        action="customer.created",
        target_type="client_account",
        target_id=account.id,
        module_key="ordering",
        summary=f"Customer {account.name} created for ordering.",
        **_actor(session),
    )
    db.commit()
    db.refresh(account)
    return _serialize_customer(db, account)


@router.patch("/customers/{customer_id}")
def update_customer(
    customer_id: int,
    payload: CustomerUpdate,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    account = _customer_or_404(db, customer_id)
    for field_name, value in payload.model_dump(exclude_unset=True).items():
        setattr(account, field_name, value)
    record_audit_event(
        db,
        tenant_id=account.tenant_id,
        client_account_id=account.id,
        action="customer.updated",
        target_type="client_account",
        target_id=account.id,
        module_key="ordering",
        summary=f"Customer {account.name} updated (status {account.status}).",
        **_actor(session),
    )
    db.commit()
    db.refresh(account)
    return _serialize_customer(db, account)


# --- Customer users ----------------------------------------------------------


def _serialize_user(user: ClientUser) -> dict:
    return {
        "id": user.id,
        "client_account_id": user.client_account_id,
        "full_name": user.full_name,
        "email": user.email,
        "role": user.role,
        "status": user.status,
        "created_at": user.created_at,
    }


@router.get("/customers/{customer_id}/users")
def list_customer_users(
    customer_id: int,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    _customer_or_404(db, customer_id)
    users = db.scalars(
        select(ClientUser).where(ClientUser.client_account_id == customer_id).order_by(ClientUser.full_name)
    ).all()
    return [_serialize_user(u) for u in users]


@router.post("/customers/{customer_id}/users", status_code=status.HTTP_201_CREATED)
def create_customer_user(
    customer_id: int,
    payload: CustomerUserCreate,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    account = _customer_or_404(db, customer_id)
    user = ClientUser(
        tenant_id=account.tenant_id,
        client_account_id=account.id,
        full_name=payload.full_name,
        email=payload.email,
        role=payload.role,
        status="invited",
    )
    db.add(user)
    try:
        db.flush()
    except IntegrityError as exc:
        db.rollback()
        raise HTTPException(status_code=409, detail="A user with that email already exists for this customer") from exc
    ensure_user_module_permissions(db, user)
    record_audit_event(
        db,
        tenant_id=account.tenant_id,
        client_account_id=account.id,
        action="user.created",
        target_type="client_user",
        target_id=user.id,
        module_key="ordering",
        summary=f"{user.full_name} invited as {user.role}.",
        **_actor(session),
    )
    db.commit()
    db.refresh(user)
    return _serialize_user(user)


@router.patch("/customers/{customer_id}/users/{user_id}")
def update_customer_user(
    customer_id: int,
    user_id: int,
    payload: CustomerUserUpdate,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    account = _customer_or_404(db, customer_id)
    user = db.scalar(
        select(ClientUser).where(ClientUser.id == user_id, ClientUser.client_account_id == customer_id)
    )
    if user is None:
        raise HTTPException(status_code=404, detail="User not found")
    changes = payload.model_dump(exclude_unset=True)
    original_role = user.role
    for field_name, value in changes.items():
        setattr(user, field_name, value)
    if "role" in changes and changes["role"] != original_role:
        from app.services.client_access_service import default_access_level_for_role

        for permission in user.module_permissions:
            permission.access_level = default_access_level_for_role(user.role, permission.module_key)
    record_audit_event(
        db,
        tenant_id=account.tenant_id,
        client_account_id=account.id,
        action="user.updated",
        target_type="client_user",
        target_id=user.id,
        module_key="ordering",
        summary=f"{user.full_name} updated (role {user.role}, status {user.status}).",
        **_actor(session),
    )
    db.commit()
    db.refresh(user)
    return _serialize_user(user)


# --- Categories --------------------------------------------------------------


def _serialize_category(c: ProductCategory) -> dict:
    return {
        "id": c.id,
        "slug": c.slug,
        "name": c.name,
        "description": c.description,
        "sort_order": c.sort_order,
        "active": c.active,
    }


@router.get("/categories")
def list_categories(
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    rows = db.scalars(
        select(ProductCategory).where(ProductCategory.tenant_id == TENANT).order_by(ProductCategory.sort_order, ProductCategory.name)
    ).all()
    return [_serialize_category(c) for c in rows]


@router.post("/categories", status_code=status.HTTP_201_CREATED)
def create_category(
    payload: CategoryCreate,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    category = ProductCategory(tenant_id=TENANT, **payload.model_dump())
    db.add(category)
    try:
        db.commit()
    except IntegrityError as exc:
        db.rollback()
        raise HTTPException(status_code=409, detail="A category with that slug already exists") from exc
    db.refresh(category)
    return _serialize_category(category)


@router.patch("/categories/{category_id}")
def update_category(
    category_id: int,
    payload: CategoryUpdate,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    category = db.scalar(
        select(ProductCategory).where(ProductCategory.id == category_id, ProductCategory.tenant_id == TENANT)
    )
    if category is None:
        raise HTTPException(status_code=404, detail="Category not found")
    for field_name, value in payload.model_dump(exclude_unset=True).items():
        setattr(category, field_name, value)
    db.commit()
    db.refresh(category)
    return _serialize_category(category)


# --- Products (catalogue) ----------------------------------------------------


@router.get("/products")
def list_products(
    include_inactive: bool = Query(default=True),
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    stmt = select(CatalogueProduct).where(CatalogueProduct.tenant_id == TENANT)
    if not include_inactive:
        stmt = stmt.where(CatalogueProduct.active.is_(True))
    products = db.scalars(stmt.order_by(CatalogueProduct.category, CatalogueProduct.name)).all()
    return [svc.serialize_product(p) for p in products]


@router.post("/products", status_code=status.HTTP_201_CREATED)
def create_product(
    payload: CatalogueProductCreate,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    product = CatalogueProduct(tenant_id=TENANT, **payload.model_dump())
    db.add(product)
    try:
        db.commit()
    except IntegrityError as exc:
        db.rollback()
        raise HTTPException(status_code=409, detail="A product with that SKU already exists") from exc
    db.refresh(product)
    return svc.serialize_product(product)


@router.patch("/products/{product_id}")
def update_product(
    product_id: int,
    payload: CatalogueProductUpdate,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    product = _product_or_404(db, product_id)
    for field_name, value in payload.model_dump(exclude_unset=True).items():
        setattr(product, field_name, value)
    try:
        db.commit()
    except IntegrityError as exc:
        db.rollback()
        raise HTTPException(status_code=409, detail="A product with that SKU already exists") from exc
    db.refresh(product)
    return svc.serialize_product(product)


# --- Per-customer visibility -------------------------------------------------


@router.get("/customers/{customer_id}/visibility")
def get_customer_visibility(
    customer_id: int,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    _customer_or_404(db, customer_id)
    hidden = svc.visible_product_ids_for_customer(db, tenant_id=TENANT, client_account_id=customer_id)
    products = db.scalars(
        select(CatalogueProduct).where(CatalogueProduct.tenant_id == TENANT).order_by(CatalogueProduct.name)
    ).all()
    return [
        {"product_id": p.id, "name": p.name, "sku": p.sku, "category": p.category, "visible": p.id not in hidden}
        for p in products
    ]


@router.put("/customers/{customer_id}/visibility")
def set_customer_visibility(
    customer_id: int,
    payload: VisibilityUpdate,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    account = _customer_or_404(db, customer_id)
    _product_or_404(db, payload.product_id)
    row = db.scalar(
        select(CustomerProductVisibility).where(
            CustomerProductVisibility.client_account_id == customer_id,
            CustomerProductVisibility.product_id == payload.product_id,
        )
    )
    if row is None:
        row = CustomerProductVisibility(
            tenant_id=TENANT,
            client_account_id=customer_id,
            product_id=payload.product_id,
            visible=payload.visible,
        )
        db.add(row)
    else:
        row.visible = payload.visible
    record_audit_event(
        db,
        tenant_id=account.tenant_id,
        client_account_id=account.id,
        action="visibility.updated",
        target_type="catalogue_product",
        target_id=payload.product_id,
        module_key="ordering",
        summary=f"Product {payload.product_id} {'shown to' if payload.visible else 'hidden from'} {account.name}.",
        **_actor(session),
    )
    db.commit()
    return {"product_id": payload.product_id, "visible": payload.visible}


# --- Price lists -------------------------------------------------------------


def _serialize_price_list(db: Session, pl: PriceList) -> dict:
    items = db.scalars(select(PriceListItem).where(PriceListItem.price_list_id == pl.id)).all()
    return {
        "id": pl.id,
        "code": pl.code,
        "name": pl.name,
        "description": pl.description,
        "active": pl.active,
        "items": [
            {
                "id": it.id,
                "product_id": it.product_id,
                "unit_price": it.unit_price,
                "tiers": _serialize_tiers(db, price_list_item_id=it.id),
            }
            for it in items
        ],
    }


@router.get("/price-lists")
def list_price_lists(
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    rows = db.scalars(select(PriceList).where(PriceList.tenant_id == TENANT).order_by(PriceList.name)).all()
    return [_serialize_price_list(db, pl) for pl in rows]


@router.post("/price-lists", status_code=status.HTTP_201_CREATED)
def create_price_list(
    payload: PriceListCreate,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    price_list = PriceList(tenant_id=TENANT, **payload.model_dump())
    db.add(price_list)
    try:
        db.commit()
    except IntegrityError as exc:
        db.rollback()
        raise HTTPException(status_code=409, detail="A price list with that code already exists") from exc
    db.refresh(price_list)
    return _serialize_price_list(db, price_list)


@router.put("/price-lists/{price_list_id}/items")
def upsert_price_list_item(
    price_list_id: int,
    payload: PriceListItemUpsert,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    price_list = db.scalar(
        select(PriceList).where(PriceList.id == price_list_id, PriceList.tenant_id == TENANT)
    )
    if price_list is None:
        raise HTTPException(status_code=404, detail="Price list not found")
    _product_or_404(db, payload.product_id)
    item = db.scalar(
        select(PriceListItem).where(
            PriceListItem.price_list_id == price_list_id, PriceListItem.product_id == payload.product_id
        )
    )
    if item is None:
        item = PriceListItem(
            tenant_id=TENANT, price_list_id=price_list_id, product_id=payload.product_id, unit_price=payload.unit_price
        )
        db.add(item)
        db.flush()
    else:
        item.unit_price = payload.unit_price
    if payload.tiers is not None:
        db.query(PriceTier).filter(PriceTier.price_list_item_id == item.id).delete()
        for tier in payload.tiers:
            db.add(
                PriceTier(
                    tenant_id=TENANT,
                    price_list_item_id=item.id,
                    min_quantity=tier.min_quantity,
                    unit_price=tier.unit_price,
                )
            )
    db.commit()
    db.refresh(price_list)
    return _serialize_price_list(db, price_list)


# --- Customer pricing --------------------------------------------------------


def _serialize_customer_pricing(db: Session, customer_id: int) -> dict:
    assignment = db.scalar(
        select(CustomerPriceAssignment).where(CustomerPriceAssignment.client_account_id == customer_id)
    )
    product_prices = db.scalars(
        select(CustomerProductPrice).where(CustomerProductPrice.client_account_id == customer_id)
    ).all()
    return {
        "customer_id": customer_id,
        "price_list_id": assignment.price_list_id if assignment else None,
        "discount_percent": assignment.discount_percent if assignment else 0.0,
        "product_prices": [
            {
                "id": pp.id,
                "product_id": pp.product_id,
                "unit_price": pp.unit_price,
                "rule_type": pp.rule_type,
                "contract_reference": pp.contract_reference,
                "notes": pp.notes,
                "active": pp.active,
                "tiers": _serialize_tiers(db, customer_product_price_id=pp.id),
            }
            for pp in product_prices
        ],
    }


@router.get("/customers/{customer_id}/pricing")
def get_customer_pricing(
    customer_id: int,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    _customer_or_404(db, customer_id)
    return _serialize_customer_pricing(db, customer_id)


@router.put("/customers/{customer_id}/assignment")
def upsert_customer_assignment(
    customer_id: int,
    payload: CustomerPriceAssignmentUpsert,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    account = _customer_or_404(db, customer_id)
    if payload.price_list_id is not None:
        if db.scalar(select(PriceList.id).where(PriceList.id == payload.price_list_id, PriceList.tenant_id == TENANT)) is None:
            raise HTTPException(status_code=404, detail="Price list not found")
    assignment = db.scalar(
        select(CustomerPriceAssignment).where(CustomerPriceAssignment.client_account_id == customer_id)
    )
    if assignment is None:
        assignment = CustomerPriceAssignment(
            tenant_id=TENANT,
            client_account_id=customer_id,
            price_list_id=payload.price_list_id,
            discount_percent=payload.discount_percent,
        )
        db.add(assignment)
    else:
        assignment.price_list_id = payload.price_list_id
        assignment.discount_percent = payload.discount_percent
    record_audit_event(
        db,
        tenant_id=account.tenant_id,
        client_account_id=account.id,
        action="pricing.assignment_updated",
        target_type="customer_price_assignment",
        target_id=None,
        module_key="ordering",
        summary=f"{account.name} assigned price list {payload.price_list_id} at {payload.discount_percent:g}% discount.",
        **_actor(session),
    )
    db.commit()
    return _serialize_customer_pricing(db, customer_id)


@router.put("/customers/{customer_id}/product-prices")
def upsert_customer_product_price(
    customer_id: int,
    payload: CustomerProductPriceUpsert,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    account = _customer_or_404(db, customer_id)
    _product_or_404(db, payload.product_id)
    cpp = db.scalar(
        select(CustomerProductPrice).where(
            CustomerProductPrice.client_account_id == customer_id,
            CustomerProductPrice.product_id == payload.product_id,
        )
    )
    if cpp is None:
        cpp = CustomerProductPrice(
            tenant_id=TENANT,
            client_account_id=customer_id,
            product_id=payload.product_id,
            unit_price=payload.unit_price,
            rule_type=payload.rule_type,
            contract_reference=payload.contract_reference,
            notes=payload.notes,
            active=payload.active,
        )
        db.add(cpp)
        db.flush()
    else:
        cpp.unit_price = payload.unit_price
        cpp.rule_type = payload.rule_type
        cpp.contract_reference = payload.contract_reference
        cpp.notes = payload.notes
        cpp.active = payload.active
    if payload.tiers is not None:
        db.query(PriceTier).filter(PriceTier.customer_product_price_id == cpp.id).delete()
        for tier in payload.tiers:
            db.add(
                PriceTier(
                    tenant_id=TENANT,
                    customer_product_price_id=cpp.id,
                    min_quantity=tier.min_quantity,
                    unit_price=tier.unit_price,
                )
            )
    record_audit_event(
        db,
        tenant_id=account.tenant_id,
        client_account_id=account.id,
        action="pricing.product_price_updated",
        target_type="customer_product_price",
        target_id=cpp.id,
        module_key="ordering",
        summary=f"{account.name} {payload.rule_type} price set for product {payload.product_id}.",
        **_actor(session),
    )
    db.commit()
    return _serialize_customer_pricing(db, customer_id)


@router.delete("/customers/{customer_id}/product-prices/{product_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_customer_product_price(
    customer_id: int,
    product_id: int,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    _customer_or_404(db, customer_id)
    cpp = db.scalar(
        select(CustomerProductPrice).where(
            CustomerProductPrice.client_account_id == customer_id,
            CustomerProductPrice.product_id == product_id,
        )
    )
    if cpp is not None:
        db.query(PriceTier).filter(PriceTier.customer_product_price_id == cpp.id).delete()
        db.delete(cpp)
        db.commit()
    return Response(status_code=status.HTTP_204_NO_CONTENT)


# --- Orders ------------------------------------------------------------------


@router.get("/orders")
def list_all_orders(
    status_filter: str | None = Query(default=None, alias="status"),
    customer_id: int | None = Query(default=None),
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    stmt = (
        select(Order)
        .where(Order.tenant_id == TENANT)
        .options(selectinload(Order.lines))
        .order_by(Order.created_at.desc())
    )
    if status_filter:
        stmt = stmt.where(Order.status == status_filter)
    if customer_id is not None:
        stmt = stmt.where(Order.client_account_id == customer_id)
    orders = db.scalars(stmt).all()
    # Hide drafts from the admin queue by default unless explicitly requested.
    if not status_filter:
        orders = [o for o in orders if o.status != "draft"]
    result = []
    for o in orders:
        data = svc.serialize_order(o, for_admin=True)
        account = db.scalar(select(ClientAccount.name).where(ClientAccount.id == o.client_account_id))
        data["customer_name"] = account
        result.append(data)
    return result


@router.get("/orders/{order_id}")
def get_order_admin(
    order_id: int,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    order = _load_order(db, order_id)
    data = svc.serialize_order(order, for_admin=True)
    account = db.scalar(select(ClientAccount).where(ClientAccount.id == order.client_account_id))
    data["customer_name"] = account.name if account else None
    return data


@router.patch("/orders/{order_id}/status")
def update_order_status(
    order_id: int,
    payload: OrderStatusUpdate,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    order = _load_order(db, order_id)
    if order.status == payload.to_status:
        raise HTTPException(status_code=409, detail="Order is already in that status")
    if not svc.can_admin_transition(order.status, payload.to_status):
        raise HTTPException(
            status_code=409,
            detail=f"Cannot move an order from {order.status} to {payload.to_status}",
        )
    from_status = order.status
    svc.record_status_change(
        db, order, to_status=payload.to_status, actor_type="lean_admin", actor_name=session.name, note=payload.note
    )
    svc.audit_order_event(
        db, session=session, order=order, action="order.status_changed",
        summary=f"Order {order.order_number or order.id} moved {from_status} → {payload.to_status}.",
    )
    db.commit()
    db.refresh(order)
    return svc.serialize_order(order, for_admin=True)


@router.patch("/orders/{order_id}/lines/{line_id}")
def override_order_line(
    order_id: int,
    line_id: int,
    payload: OrderLineOverride,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    order = _load_order(db, order_id)
    if order.status in {"completed", "cancelled", "sent_to_xero"}:
        raise HTTPException(status_code=409, detail="This order can no longer be adjusted")
    line = next((l for l in order.lines if l.id == line_id), None)
    if line is None:
        raise HTTPException(status_code=404, detail="Order line not found")
    changes = payload.model_dump(exclude_unset=True)
    if "quantity" in changes and changes["quantity"] is not None:
        line.quantity = changes["quantity"]
    if "unit_price" in changes and changes["unit_price"] is not None:
        line.admin_override_price = changes["unit_price"]
        line.requires_quote = False
        line.price_source = "fixed"
    if "reason" in changes:
        line.admin_override_reason = changes["reason"]
    svc.recompute_order_totals(order)
    svc.audit_order_event(
        db, session=session, order=order, action="order.price_override",
        summary=f"Line {line.product_name} overridden on order {order.order_number or order.id}.",
        target_id=line.id,
    )
    db.commit()
    db.refresh(order)
    return svc.serialize_order(order, for_admin=True)


@router.post("/orders/{order_id}/reopen")
def reopen_order(
    order_id: int,
    payload: ReopenOrderRequest,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    order = _load_order(db, order_id)
    if order.status in {"completed", "cancelled"}:
        raise HTTPException(status_code=409, detail="Completed or cancelled orders cannot be reopened")
    if order.status == "draft":
        raise HTTPException(status_code=409, detail="Order is already a draft")
    svc.record_status_change(
        db, order, to_status="draft", actor_type="lean_admin", actor_name=session.name,
        note=payload.note or "Reopened by admin for customer edits",
    )
    order.reopened = True
    svc.audit_order_event(
        db, session=session, order=order, action="order.reopened",
        summary=f"Order {order.order_number or order.id} reopened to draft.",
    )
    db.commit()
    db.refresh(order)
    return svc.serialize_order(order, for_admin=True)


@router.post("/orders/{order_id}/send-to-xero")
def send_order_to_xero(
    order_id: int,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    order = _load_order(db, order_id)
    if order.status not in {"confirmed", "in_production", "sent_to_xero"}:
        raise HTTPException(status_code=409, detail="Only confirmed orders can be sent to Xero")
    account = db.scalar(select(ClientAccount).where(ClientAccount.id == order.client_account_id))
    link = _xero_link_for(db, order.client_account_id)
    result = submit_order_to_xero(order, account, link)
    if link is not None and result.status == "success":
        link.last_synced_at = datetime.utcnow()

    db.add(
        XeroSyncLog(
            tenant_id=TENANT,
            order_id=order.id,
            status=result.status,
            request_summary=result.request_summary,
            xero_invoice_id=result.xero_invoice_id,
            response_message=result.message,
        )
    )
    order.xero_status = result.status
    if result.status == "success":
        order.xero_invoice_id = result.xero_invoice_id
        if order.status != "sent_to_xero" and svc.can_admin_transition(order.status, "sent_to_xero"):
            svc.record_status_change(
                db, order, to_status="sent_to_xero", actor_type="lean_admin", actor_name=session.name,
                note=f"Xero invoice {result.xero_invoice_id}",
            )
    svc.audit_order_event(
        db, session=session, order=order, action="order.xero_submit",
        summary=f"Xero submission {result.status} for order {order.order_number or order.id} ({result.message}).",
    )
    db.commit()
    db.refresh(order)
    data = svc.serialize_order(order, for_admin=True)
    data["xero_result"] = {
        "status": result.status,
        "stubbed": result.stubbed,
        "message": result.message,
        "xero_invoice_id": result.xero_invoice_id,
    }
    return data


# --- Settings / status -------------------------------------------------------


@router.get("/notification-settings")
def get_notification_settings(
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    settings_row = get_or_create_settings(db, TENANT)
    db.commit()
    return {
        "internal_recipients": settings_row.internal_recipients,
        "send_customer_confirmation": settings_row.send_customer_confirmation,
        "require_po_number": settings_row.require_po_number,
        "from_email": settings_row.from_email,
    }


@router.patch("/notification-settings")
def update_notification_settings(
    payload: NotificationSettingsUpdate,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    settings_row = get_or_create_settings(db, TENANT)
    for field_name, value in payload.model_dump(exclude_unset=True).items():
        setattr(settings_row, field_name, value)
    db.commit()
    return {
        "internal_recipients": settings_row.internal_recipients,
        "send_customer_confirmation": settings_row.send_customer_confirmation,
        "require_po_number": settings_row.require_po_number,
        "from_email": settings_row.from_email,
    }


@router.get("/xero/status")
def get_xero_status(
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    recent = db.scalars(
        select(XeroSyncLog).where(XeroSyncLog.tenant_id == TENANT).order_by(XeroSyncLog.created_at.desc()).limit(20)
    ).all()
    total_customers = db.scalar(
        select(func.count()).select_from(ClientAccount)
    ) or 0
    linked_customers = db.scalar(
        select(func.count()).select_from(XeroContactLink).where(XeroContactLink.tenant_id == TENANT)
    ) or 0
    return {
        "connection": xero_status_snapshot(),
        "contact_links": {
            "linked": linked_customers,
            "total": total_customers,
            "unlinked": max(total_customers - linked_customers, 0),
        },
        "recent_syncs": [
            {
                "id": log.id,
                "order_id": log.order_id,
                "status": log.status,
                "xero_invoice_id": log.xero_invoice_id,
                "response_message": log.response_message,
                "created_at": log.created_at,
            }
            for log in recent
        ],
    }


# --- Xero contact mapping ----------------------------------------------------


@router.get("/xero/contacts")
def list_available_xero_contacts(
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    """The Xero contacts available to link a customer against (stub or live)."""
    contacts, stubbed = list_xero_contacts()
    return {"contacts": [c.as_dict() for c in contacts], "stubbed": stubbed}


@router.get("/xero/contact-links")
def list_xero_contact_links(
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    """Every customer with its current Xero link and a suggested match.

    The suggestion is a best-effort name match against the available contacts so
    the operator can confirm rather than hunt through a dropdown.
    """
    contacts, _ = list_xero_contacts()
    by_name = {c.name.strip().lower(): c for c in contacts}

    accounts = db.scalars(select(ClientAccount).order_by(ClientAccount.name)).all()
    rows = []
    for account in accounts:
        link = _xero_link_for(db, account.id)
        suggestion = None if link else by_name.get(account.name.strip().lower())
        rows.append(
            {
                "customer_id": account.id,
                "customer_name": account.name,
                "client_code": account.client_code,
                "linked": link is not None,
                "xero_contact_id": link.xero_contact_id if link else None,
                "xero_contact_name": link.xero_contact_name if link else None,
                "xero_contact_email": link.xero_contact_email if link else None,
                "last_synced_at": link.last_synced_at if link else None,
                "suggested_contact_id": suggestion.contact_id if suggestion else None,
            }
        )
    return rows


@router.put("/customers/{customer_id}/xero-link")
def link_customer_to_xero(
    customer_id: int,
    payload: XeroContactLinkUpsert,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    account = _customer_or_404(db, customer_id)
    # Default the cached name/email from the known contact list when the caller
    # only sends an id, so the mapping reads nicely without a live round-trip.
    name = payload.xero_contact_name
    email = payload.xero_contact_email
    if name is None or email is None:
        contacts, _ = list_xero_contacts()
        match = next((c for c in contacts if c.contact_id == payload.xero_contact_id), None)
        if match is not None:
            name = name or match.name
            email = email or match.email

    link = _xero_link_for(db, customer_id)
    if link is None:
        link = XeroContactLink(
            tenant_id=TENANT,
            client_account_id=customer_id,
            xero_contact_id=payload.xero_contact_id,
            xero_contact_name=name,
            xero_contact_email=email,
        )
        db.add(link)
    else:
        link.xero_contact_id = payload.xero_contact_id
        link.xero_contact_name = name
        link.xero_contact_email = email
    record_audit_event(
        db,
        tenant_id=account.tenant_id,
        client_account_id=account.id,
        action="xero.contact_linked",
        target_type="xero_contact_link",
        target_id=account.id,
        module_key="ordering",
        summary=f"{account.name} linked to Xero contact {name or payload.xero_contact_id}.",
        **_actor(session),
    )
    db.commit()
    return {
        "customer_id": customer_id,
        "linked": True,
        "xero_contact_id": link.xero_contact_id,
        "xero_contact_name": link.xero_contact_name,
        "xero_contact_email": link.xero_contact_email,
    }


@router.delete("/customers/{customer_id}/xero-link", status_code=status.HTTP_204_NO_CONTENT)
def unlink_customer_from_xero(
    customer_id: int,
    session: AuthSession = Depends(require_ordering_admin_session),
    db: Session = Depends(get_db),
):
    account = _customer_or_404(db, customer_id)
    link = _xero_link_for(db, customer_id)
    if link is not None:
        db.delete(link)
        record_audit_event(
            db,
            tenant_id=account.tenant_id,
            client_account_id=account.id,
            action="xero.contact_unlinked",
            target_type="xero_contact_link",
            target_id=account.id,
            module_key="ordering",
            summary=f"{account.name} unlinked from Xero contact.",
            **_actor(session),
        )
        db.commit()
    return Response(status_code=status.HTTP_204_NO_CONTENT)
