"""Shared ordering-portal business logic: lifecycle, visibility, totals,
serialization, and audit.

Customer isolation is enforced by callers passing ``tenant_id`` /
``client_account_id`` into every query; this module never widens that scope.
"""
from __future__ import annotations

from datetime import datetime

from sqlalchemy import select
from sqlalchemy.orm import Session

from app.api.deps import AuthSession
from app.core.config import settings
from app.models.client_access import ClientAccount
from app.models.ordering import (
    CatalogueProduct,
    CustomerProductVisibility,
    Order,
    OrderLine,
    OrderStatusHistory,
)
from app.services.client_access_service import record_audit_event
from app.services.ordering_pricing import resolve_price

# The ordering catalogue is "standard globally" — it lives under a single
# seller/ordering tenant, while individual customers (ClientAccounts) may have
# their own tenant_id. Customer isolation therefore keys on the globally-unique
# ``client_account_id``, NOT on the customer's session tenant. Admin and
# customer code both store/read ordering data under ORDERING_TENANT.
ORDERING_TENANT = settings.client_tenant_id

# --- Lifecycle ---------------------------------------------------------------

# Full internal lifecycle → the simplified status a customer sees.
CUSTOMER_VISIBLE_STATUS = {
    "draft": "draft",
    "submitted": "submitted",
    "under_review": "processing",
    "confirmed": "confirmed",
    "sent_to_xero": "confirmed",
    "in_production": "processing",
    "ready_for_pickup": "ready",
    "dispatched": "dispatched",
    "completed": "completed",
    "cancelled": "cancelled",
}

# Statuses an admin may move an order into from a given status. Cancellation is
# allowed from any non-terminal status (handled separately).
ALLOWED_ADMIN_TRANSITIONS: dict[str, set[str]] = {
    "draft": {"submitted", "cancelled"},
    "submitted": {"under_review", "confirmed", "cancelled"},
    "under_review": {"confirmed", "submitted", "cancelled"},
    "confirmed": {"sent_to_xero", "in_production", "cancelled"},
    "sent_to_xero": {"in_production", "cancelled"},
    "in_production": {"ready_for_pickup", "dispatched", "cancelled"},
    "ready_for_pickup": {"dispatched", "completed", "cancelled"},
    "dispatched": {"completed", "cancelled"},
    "completed": set(),
    "cancelled": set(),
}

# Customers may only edit an order while it is a draft.
CUSTOMER_EDITABLE_STATUSES = {"draft"}


def can_admin_transition(from_status: str, to_status: str) -> bool:
    if to_status == "cancelled" and from_status not in {"completed", "cancelled"}:
        return True
    return to_status in ALLOWED_ADMIN_TRANSITIONS.get(from_status, set())


def record_status_change(
    db: Session,
    order: Order,
    *,
    to_status: str,
    actor_type: str,
    actor_name: str | None,
    note: str | None = None,
) -> None:
    db.add(
        OrderStatusHistory(
            tenant_id=order.tenant_id,
            order_id=order.id,
            from_status=order.status,
            to_status=to_status,
            actor_type=actor_type,
            actor_name=actor_name,
            note=note,
        )
    )
    order.status = to_status


def next_order_number(db: Session, tenant_id: str) -> str:
    # Cheap monotonic-ish numbering based on max id; good enough for v1.
    last_id = db.scalar(select(Order.id).order_by(Order.id.desc()).limit(1)) or 0
    return f"ORD-{last_id + 1:06d}"


# --- Product visibility ------------------------------------------------------


def visible_product_ids_for_customer(db: Session, *, tenant_id: str, client_account_id: int) -> set[int]:
    """Ids the customer is explicitly *hidden* from (opt-out model)."""
    rows = db.scalars(
        select(CustomerProductVisibility.product_id).where(
            CustomerProductVisibility.client_account_id == client_account_id,
            CustomerProductVisibility.tenant_id == tenant_id,
            CustomerProductVisibility.visible.is_(False),
        )
    ).all()
    return set(rows)


def list_visible_products(
    db: Session, *, tenant_id: str, client_account_id: int
) -> list[CatalogueProduct]:
    hidden = visible_product_ids_for_customer(
        db, tenant_id=tenant_id, client_account_id=client_account_id
    )
    products = db.scalars(
        select(CatalogueProduct)
        .where(CatalogueProduct.tenant_id == tenant_id, CatalogueProduct.active.is_(True))
        .order_by(CatalogueProduct.category, CatalogueProduct.name)
    ).all()
    return [p for p in products if p.id not in hidden]


# --- Totals ------------------------------------------------------------------


def effective_unit_price(line: OrderLine) -> float | None:
    if line.admin_override_price is not None:
        return line.admin_override_price
    return line.unit_price


def recompute_order_totals(order: Order) -> None:
    subtotal = 0.0
    requires_quote = False
    for line in order.lines:
        unit = effective_unit_price(line)
        if line.requires_quote or unit is None:
            requires_quote = True
            line.line_total = None
            continue
        line.line_total = round(unit * line.quantity, 4)
        subtotal += line.line_total
    order.subtotal_ex_gst = round(subtotal, 4)
    order.requires_quote = requires_quote


# --- Serialization -----------------------------------------------------------


def serialize_product(
    product: CatalogueProduct,
    *,
    db: Session | None = None,
    client_account_id: int | None = None,
    quantity: float = 1.0,
) -> dict:
    """Serialize a catalogue product. When ``db`` + ``client_account_id`` are
    given, attach the resolved customer-specific price + provenance."""
    data = {
        "id": product.id,
        "name": product.name,
        "sku": product.sku,
        "description": product.description,
        "category": product.category,
        "image_url": product.image_url,
        "unit_size": product.unit_size,
        "unit_of_measure": product.unit_of_measure,
        "min_order_quantity": product.min_order_quantity,
        "stock_status": product.stock_status,
        "active": product.active,
        "requires_quote": product.requires_quote,
        "base_price": product.base_price,
        "created_at": product.created_at,
    }
    if db is not None and client_account_id is not None:
        resolution = resolve_price(
            db, client_account_id=client_account_id, product=product, quantity=quantity
        )
        data["price"] = {
            "unit_price": resolution.unit_price,
            "price_source": resolution.price_source,
            "price_rule_id": resolution.price_rule_id,
            "discount_percent": resolution.discount_percent,
            "requires_quote": resolution.requires_quote,
            "label": resolution.label,
        }
    return data


def serialize_order_line(line: OrderLine, *, for_admin: bool) -> dict:
    data = {
        "id": line.id,
        "product_id": line.product_id,
        "product_name": line.product_name,
        "product_sku": line.product_sku,
        "quantity": line.quantity,
        "unit_price": effective_unit_price(line),
        "line_total": line.line_total,
        "requires_quote": line.requires_quote,
        "price_source": line.price_source,
        "discount_percent": line.discount_percent,
        "notes": line.notes,
    }
    if for_admin:
        data.update(
            {
                "resolved_unit_price": line.unit_price,
                "admin_override_price": line.admin_override_price,
                "admin_override_reason": line.admin_override_reason,
                "price_rule_id": line.price_rule_id,
            }
        )
    return data


def serialize_order(order: Order, *, for_admin: bool) -> dict:
    status = order.status if for_admin else CUSTOMER_VISIBLE_STATUS.get(order.status, order.status)
    data = {
        "id": order.id,
        "order_number": order.order_number,
        "status": status,
        "client_account_id": order.client_account_id,
        "created_by_name": order.created_by_name,
        "purchase_order_number": order.purchase_order_number,
        "delivery_notes": order.delivery_notes,
        "requested_delivery_date": order.requested_delivery_date,
        "fulfilment_method": order.fulfilment_method,
        "subtotal_ex_gst": order.subtotal_ex_gst,
        "requires_quote": order.requires_quote,
        "submitted_at": order.submitted_at,
        "created_at": order.created_at,
        "updated_at": order.updated_at,
        "editable": order.status in CUSTOMER_EDITABLE_STATUSES,
        "lines": [serialize_order_line(line, for_admin=for_admin) for line in order.lines],
    }
    if for_admin:
        data.update(
            {
                "raw_status": order.status,
                "admin_notes": order.admin_notes,
                "reopened": order.reopened,
                "xero_status": order.xero_status,
                "xero_invoice_id": order.xero_invoice_id,
                "status_history": [
                    {
                        "id": h.id,
                        "from_status": h.from_status,
                        "to_status": h.to_status,
                        "actor_type": h.actor_type,
                        "actor_name": h.actor_name,
                        "note": h.note,
                        "created_at": h.created_at,
                    }
                    for h in order.status_history
                ],
            }
        )
    return data


# --- Audit -------------------------------------------------------------------


def audit_order_event(
    db: Session,
    *,
    session: AuthSession,
    order: Order,
    action: str,
    summary: str,
    target_id: int | None = None,
) -> None:
    """Record an ordering audit event onto the shared audit trail."""
    actor_type = "lean_admin" if session.role in {"admin", "internal"} else "customer"
    record_audit_event(
        db,
        tenant_id=order.tenant_id,
        client_account_id=order.client_account_id,
        actor_type=actor_type,
        actor_name=session.name or "",
        actor_email=session.email or "",
        actor_role=session.client_role or session.role,
        action=action,
        target_type="order",
        target_id=target_id if target_id is not None else order.id,
        module_key="ordering",
        summary=summary,
    )


def get_customer_account(db: Session, *, client_account_id: int) -> ClientAccount | None:
    # client_account_id is a global primary key; a customer's own tenant_id may
    # differ from ORDERING_TENANT, so we look up by id alone.
    return db.scalar(select(ClientAccount).where(ClientAccount.id == client_account_id))


def ensure_customer_active(account: ClientAccount | None) -> None:
    from fastapi import HTTPException, status

    if account is None:
        raise HTTPException(status_code=404, detail="Customer account not found")
    if account.status != "active":
        raise HTTPException(status_code=403, detail="This customer account is disabled")
