"""Cross-model grouping for analytical queries.

Handles cases where the grouping field lives on a related model
(e.g., group pos.order by product category requires traversing
pos.order → pos.order.line → product.product → pos.category).
"""

from __future__ import annotations

from collections import OrderedDict
from dataclasses import dataclass

from cli_anything.odoo_12.utils.odoo_backend import OdooBackend


@dataclass
class CrossGroupDef:
    """Definition of a cross-model grouping path."""
    line_model: str         # e.g. "pos.order.line"
    line_fk: str            # FK from line to parent: "order_id"
    product_field: str      # field on line pointing to product: "product_id"
    category_field: str     # field on product pointing to category: "pos_categ_id"
    category_model: str     # the category model: "pos.category"
    qty_field: str          # quantity field on line: "qty"
    amount_field: str       # amount field on line: "price_subtotal_incl"
    label: str              # human label: "Categoria POS"


# ── Registry ──────────────────────────────────────────────────
# (parent_model, group_keyword) → CrossGroupDef

CROSS_GROUP_REGISTRY: dict[tuple[str, str], CrossGroupDef] = {
    # pos.order → categoria POS do produto
    ("pos.order", "pos_categ"): CrossGroupDef(
        line_model="pos.order.line",
        line_fk="order_id",
        product_field="product_id",
        category_field="pos_categ_id",
        category_model="pos.category",
        qty_field="qty",
        amount_field="price_subtotal_incl",
        label="Categoria POS",
    ),
    # pos.order → categoria interna do produto
    ("pos.order", "product_categ"): CrossGroupDef(
        line_model="pos.order.line",
        line_fk="order_id",
        product_field="product_id",
        category_field="categ_id",
        category_model="product.category",
        qty_field="qty",
        amount_field="price_subtotal_incl",
        label="Categoria de Produto",
    ),
    # account.invoice → categoria do produto
    ("account.invoice", "product_categ"): CrossGroupDef(
        line_model="account.invoice.line",
        line_fk="invoice_id",
        product_field="product_id",
        category_field="categ_id",
        category_model="product.category",
        qty_field="quantity",
        amount_field="price_subtotal",
        label="Categoria de Produto",
    ),
    # sale.order → categoria do produto
    ("sale.order", "product_categ"): CrossGroupDef(
        line_model="sale.order.line",
        line_fk="order_id",
        product_field="product_id",
        category_field="categ_id",
        category_model="product.category",
        qty_field="product_uom_qty",
        amount_field="price_subtotal",
        label="Categoria de Produto",
    ),
    # purchase.order → categoria do produto
    ("purchase.order", "product_categ"): CrossGroupDef(
        line_model="purchase.order.line",
        line_fk="order_id",
        product_field="product_id",
        category_field="categ_id",
        category_model="product.category",
        qty_field="product_qty",
        amount_field="price_subtotal",
        label="Categoria de Produto",
    ),
    # pos.order → por produto individual
    ("pos.order", "product"): CrossGroupDef(
        line_model="pos.order.line",
        line_fk="order_id",
        product_field="product_id",
        category_field="__self__",  # sentinel: group by product itself
        category_model="product.product",
        qty_field="qty",
        amount_field="price_subtotal_incl",
        label="Produto",
    ),
    # account.invoice → por produto individual
    ("account.invoice", "product"): CrossGroupDef(
        line_model="account.invoice.line",
        line_fk="invoice_id",
        product_field="product_id",
        category_field="__self__",
        category_model="product.product",
        qty_field="quantity",
        amount_field="price_subtotal",
        label="Produto",
    ),
    # sale.order → por produto individual
    ("sale.order", "product"): CrossGroupDef(
        line_model="sale.order.line",
        line_fk="order_id",
        product_field="product_id",
        category_field="__self__",
        category_model="product.product",
        qty_field="product_uom_qty",
        amount_field="price_subtotal",
        label="Produto",
    ),
    # purchase.order → por produto individual
    ("purchase.order", "product"): CrossGroupDef(
        line_model="purchase.order.line",
        line_fk="order_id",
        product_field="product_id",
        category_field="categ_id",
        category_model="product.category",
        qty_field="product_qty",
        amount_field="price_subtotal",
        label="Produto",
    ),
}

# ── Keyword → group_key mapping ──────────────────────────────
# Longest-match-first for NLQ detection

CROSS_GROUP_KEYWORDS: dict[str, str] = {
    # PT-BR
    "categoria de produto": "product_categ",
    "categoria do produto": "product_categ",
    "categoria de produtos": "product_categ",
    "categoria dos produtos": "product_categ",
    "categoria pos": "pos_categ",
    "categoria do pos": "pos_categ",
    "categoria do ponto de venda": "pos_categ",
    "categoria": "pos_categ",  # default for POS context; product_categ otherwise
    "produto": "product",
    "produtos": "product",
    # EN
    "product category": "product_categ",
    "pos category": "pos_categ",
    "product": "product",
    "products": "product",
    # ES
    "categoría de producto": "product_categ",
    "categoría pos": "pos_categ",
}

_SORTED_CG_KEYWORDS = sorted(CROSS_GROUP_KEYWORDS.keys(), key=len, reverse=True)


def resolve_cross_group(model: str, text: str) -> str | None:
    """Find the cross-group key from query text, if one exists for this model.

    Returns the group_key (e.g. "pos_categ", "product_categ", "product") or None.
    """
    text_lower = text.lower()
    for kw in _SORTED_CG_KEYWORDS:
        if kw in text_lower:
            group_key = CROSS_GROUP_KEYWORDS[kw]
            # "categoria" alone: prefer pos_categ for pos.order, product_categ otherwise
            if kw == "categoria" and group_key == "pos_categ" and model != "pos.order":
                group_key = "product_categ"
            if (model, group_key) in CROSS_GROUP_REGISTRY:
                return group_key
    return None


@dataclass
class CrossGroupResult:
    """Result of a cross-model grouping query."""
    groups: list[dict]       # [{name, count, qty, total, pct}, ...]
    grand_count: int
    grand_qty: float
    grand_total: float
    label: str               # e.g. "Categoria POS"


def execute_cross_group(
    rpc: OdooBackend,
    parent_ids: list[int],
    defn: CrossGroupDef,
) -> CrossGroupResult:
    """Execute a cross-model grouping query.

    1. Fetch lines for the given parent IDs
    2. Fetch products from those lines
    3. Fetch category from products
    4. Aggregate by category
    """
    if not parent_ids:
        return CrossGroupResult([], 0, 0.0, 0.0, defn.label)

    # 1. Fetch lines
    line_fields = [defn.product_field, defn.qty_field, defn.amount_field]
    lines = rpc.search_read(
        defn.line_model,
        [(defn.line_fk, "in", parent_ids)],
        fields=line_fields,
        limit=0,
    )

    if not lines:
        return CrossGroupResult([], 0, 0.0, 0.0, defn.label)

    # 2. Collect unique product IDs
    product_ids = list(set(
        line[defn.product_field][0]
        for line in lines
        if isinstance(line.get(defn.product_field), (list, tuple))
        and len(line[defn.product_field]) == 2
    ))

    # 3. Resolve category for each product
    prod_category: dict[int, str] = {}

    if defn.category_field == "__self__":
        # Group by product itself — use product name
        if product_ids:
            products = rpc.read("product.product", product_ids, fields=["id", "name"])
            for p in products:
                prod_category[p["id"]] = p.get("name", f"Produto #{p['id']}")
    else:
        # Group by category field on product
        if product_ids:
            products = rpc.read(
                "product.product", product_ids,
                fields=["id", defn.category_field],
            )
            # Find root categories to treat as "Sem Categoria"
            _root_ids = set()
            try:
                roots = rpc.search_read(
                    defn.category_model,
                    [("parent_id", "=", False)],
                    fields=["id", "name"],
                    limit=0,
                )
                for r in roots:
                    # Generic root names that aren't real categories
                    if r.get("name", "").lower() in ("all", "todas", "todos", "root", "/"):
                        _root_ids.add(r["id"])
            except Exception:
                pass

            for p in products:
                cat = p.get(defn.category_field)
                if isinstance(cat, (list, tuple)) and len(cat) == 2:
                    if cat[0] in _root_ids:
                        prod_category[p["id"]] = "Sem Categoria"
                    else:
                        prod_category[p["id"]] = cat[1]
                else:
                    prod_category[p["id"]] = "Sem Categoria"

    # 4. Aggregate
    groups: dict[str, dict] = OrderedDict()

    for line in lines:
        prod = line.get(defn.product_field)
        pid = prod[0] if isinstance(prod, (list, tuple)) and len(prod) == 2 else None
        cat_name = prod_category.get(pid, "Sem Categoria") if pid else "Sem Categoria"

        if cat_name not in groups:
            groups[cat_name] = {"name": cat_name, "count": 0, "qty": 0.0, "total": 0.0}

        groups[cat_name]["count"] += 1
        groups[cat_name]["qty"] += float(line.get(defn.qty_field, 0) or 0)
        groups[cat_name]["total"] += float(line.get(defn.amount_field, 0) or 0)

    # Sort by total descending
    sorted_groups = sorted(groups.values(), key=lambda g: -g["total"])

    grand_total = sum(g["total"] for g in sorted_groups)
    for g in sorted_groups:
        g["pct"] = (g["total"] / grand_total * 100) if grand_total > 0 else 0.0

    return CrossGroupResult(
        groups=sorted_groups,
        grand_count=sum(g["count"] for g in sorted_groups),
        grand_qty=sum(g["qty"] for g in sorted_groups),
        grand_total=grand_total,
        label=defn.label,
    )
