"""Dashboard summary endpoint.

Returns only the aggregates the homepage actually renders — counts, top items,
totals, and a trend-chart series. Replaces a Dashboard load that previously
fetched five full collections (raw materials, mixes, all product cost
breakdowns, scenarios, data-quality) and only used summaries from each.
"""
from __future__ import annotations

from datetime import date
import json

from fastapi import APIRouter, Depends
from sqlalchemy import select
from sqlalchemy.orm import Session, selectinload

from app.api.deps import AuthSession, require_client_module_access
from app.db.session import get_db
from app.models.mix import Mix
from app.models.product import Product
from app.models.product_costing import ProductCostItem
from app.models.raw_material import RawMaterial
from app.models.throughput import ProductionThroughput, ThroughputProduct
from app.services.client_access_service import has_access_level
from app.services.costing_engine import (
    calculate_mix_cost,
    calculate_product_cost,
    get_active_price,
    calculate_raw_material_cost,
)


router = APIRouter(prefix="/api/dashboard", tags=["dashboard"])


def _can(session: AuthSession, module_key: str) -> bool:
    permissions = session.module_permissions or {}
    return has_access_level(permissions.get(module_key), "view")


def _month_start(today: date) -> date:
    return today.replace(day=1)


def _warnings(item: ProductCostItem) -> list[str]:
    if not item.warnings:
        return []
    try:
        parsed = json.loads(item.warnings)
        return parsed if isinstance(parsed, list) else [str(parsed)]
    except json.JSONDecodeError:
        return [item.warnings]


def _pricing_key(value: str | None) -> str:
    return (value or "").strip().lower()


def _find_pricing_item(
    entry: ProductionThroughput,
    product: ThroughputProduct | None,
    by_item_id: dict[str, ProductCostItem],
    by_name: dict[str, ProductCostItem],
) -> ProductCostItem | None:
    if product and product.item_id and product.item_id in by_item_id:
        return by_item_id[product.item_id]
    return by_name.get(_pricing_key(entry.product_name_snapshot)) or by_name.get(_pricing_key(product.name if product else None))


def _operations_summary(session: AuthSession, db: Session) -> dict | None:
    if not (_can(session, "operations_throughput") or _can(session, "products") or _can(session, "dashboard")):
        return None

    today = date.today()
    start = _month_start(today)
    entries = db.scalars(
        select(ProductionThroughput)
        .where(
            ProductionThroughput.tenant_id == session.tenant_id,
            ProductionThroughput.production_date >= start,
            ProductionThroughput.production_date <= today,
        )
        .options(selectinload(ProductionThroughput.product))
        .order_by(ProductionThroughput.production_date.desc())
    ).all()
    pricing_items = db.scalars(select(ProductCostItem).where(ProductCostItem.tenant_id == session.tenant_id)).all()
    by_item_id = {item.item_id: item for item in pricing_items if item.item_id}
    by_name: dict[str, ProductCostItem] = {}
    for item in pricing_items:
        by_name.setdefault(_pricing_key(item.product_name), item)
        by_name.setdefault(_pricing_key(item.mix_product_name), item)

    product_totals: dict[str, dict] = {}
    client_totals: dict[str, float] = {}
    produced_not_priced: dict[str, dict] = {}
    total_kg = 0.0
    total_bags = 0.0
    estimated_wholesale_value = 0.0
    wholesale_rows = 0

    for entry in entries:
        kg = entry.calculated_kg or 0.0
        bags = entry.quantity if entry.quantity_type == "bags" else 0.0
        total_kg += kg
        total_bags += bags

        product = entry.product
        name = entry.product_name_snapshot or product.name if product else entry.product_name_snapshot
        bucket = product_totals.setdefault(
            name,
            {"product_name": name, "client_name": product.client_name if product else None, "kg": 0.0, "bags": 0.0, "entries": 0},
        )
        bucket["kg"] += kg
        bucket["bags"] += bags
        bucket["entries"] += 1

        client = product.client_name if product and product.client_name else "Unassigned"
        client_totals[client] = client_totals.get(client, 0.0) + kg

        pricing = _find_pricing_item(entry, product, by_item_id, by_name)
        pricing_warnings = _warnings(pricing) if pricing else ["Missing product pricing"]
        wholesale_price = pricing.wholesale_price if pricing else None
        unit_kg = pricing.unit_kg if pricing else None
        if wholesale_price is not None:
            units = kg / unit_kg if unit_kg and unit_kg > 0 else entry.quantity
            estimated_wholesale_value += units * wholesale_price
            wholesale_rows += 1
        if pricing is None or pricing_warnings or wholesale_price is None:
            missing = produced_not_priced.setdefault(
                name,
                {
                    "product_name": name,
                    "kg": 0.0,
                    "status": "Missing pricing" if pricing is None else "Needs review",
                    "warnings": pricing_warnings[:2],
                },
            )
            missing["kg"] += kg

    issue_counts = {
        "missing_lookup": 0,
        "missing_unit_kg": 0,
        "missing_pallet_qty": 0,
        "missing_price": 0,
        "invalid_margin": 0,
    }
    for item in pricing_items:
        warnings = " ".join(_warnings(item)).lower()
        if "lookup" in warnings:
            issue_counts["missing_lookup"] += 1
        if "unit kg" in warnings:
            issue_counts["missing_unit_kg"] += 1
        if "pallet" in warnings:
            issue_counts["missing_pallet_qty"] += 1
        if item.distributor_price is None or item.wholesale_price is None:
            issue_counts["missing_price"] += 1
        if "margin" in warnings:
            issue_counts["invalid_margin"] += 1

    top_products = sorted(product_totals.values(), key=lambda row: row["kg"], reverse=True)[:5]
    clients = [
        {"client_name": client, "kg": round(kg, 2)}
        for client, kg in sorted(client_totals.items(), key=lambda item: item[1], reverse=True)[:5]
    ]
    produced_not_priced_rows = sorted(produced_not_priced.values(), key=lambda row: row["kg"], reverse=True)[:5]

    return {
        "period_label": "This month",
        "total_kg": round(total_kg, 2),
        "total_bags": round(total_bags, 2),
        "entry_count": len(entries),
        "estimated_wholesale_value": round(estimated_wholesale_value, 2),
        "priced_entry_count": wholesale_rows,
        "top_products": [
            {
                "product_name": row["product_name"],
                "client_name": row["client_name"],
                "kg": round(row["kg"], 2),
                "bags": round(row["bags"], 2),
                "entries": row["entries"],
            }
            for row in top_products
        ],
        "client_totals": clients,
        "pricing_issues": {
            **issue_counts,
            "total": sum(issue_counts.values()),
        },
        "produced_not_priced": [
            {
                "product_name": row["product_name"],
                "kg": round(row["kg"], 2),
                "status": row["status"],
                "warnings": row["warnings"],
            }
            for row in produced_not_priced_rows
        ],
    }


@router.get("/summary")
def dashboard_summary(
    session: AuthSession = Depends(require_client_module_access("dashboard")),
    db: Session = Depends(get_db),
):
    raw_materials_summary: dict | None = None
    mixes_summary: dict | None = None
    products_summary: dict | None = None
    raw_series: list[float] = []
    mix_series: list[float] = []
    product_series: list[float] = []
    operations_summary = _operations_summary(session, db)

    if _can(session, "raw_materials") or _can(session, "dashboard"):
        materials = db.scalars(
            select(RawMaterial)
            .where(RawMaterial.tenant_id == session.tenant_id)
            .options(selectinload(RawMaterial.price_versions))
        ).all()

        total_market_value = 0.0
        latest = None
        latest_date = None
        for material in materials:
            active = get_active_price(material)
            if active is None:
                continue
            comp = calculate_raw_material_cost(material, active)
            raw_series.append(comp.cost_per_kg)
            total_market_value += active.market_value
            if latest_date is None or active.effective_date > latest_date:
                latest_date = active.effective_date
                latest = {
                    "id": material.id,
                    "name": material.name,
                    "market_value": active.market_value,
                    "cost_per_kg": comp.cost_per_kg,
                    "effective_date": active.effective_date.isoformat() if active.effective_date else None,
                }

        raw_materials_summary = {
            "count": len(materials),
            "total_market_value": round(total_market_value, 4),
            "latest": latest,
        }

    if _can(session, "mix_master") or _can(session, "dashboard"):
        mix_rows = db.scalars(
            select(Mix).where(Mix.tenant_id == session.tenant_id).order_by(Mix.name)
        ).all()
        cost_sum = 0.0
        cost_count = 0
        top_mix: dict | None = None
        for mix in mix_rows:
            result = calculate_mix_cost(db, mix.id)
            cost_per_kg = result.get("mix_cost_per_kg")
            if cost_per_kg is not None:
                mix_series.append(cost_per_kg)
                cost_sum += cost_per_kg
                cost_count += 1
            if top_mix is None or (cost_per_kg or 0) > (top_mix.get("mix_cost_per_kg") or 0):
                top_mix = {
                    "id": result["id"],
                    "name": result["name"],
                    "client_name": result["client_name"],
                    "ingredients_count": len(result["ingredients"]),
                    "total_mix_kg": result["total_mix_kg"],
                    "total_mix_cost": result["total_mix_cost"],
                    "mix_cost_per_kg": cost_per_kg,
                    "warnings": result["warnings"],
                }

        mixes_summary = {
            "count": len(mix_rows),
            "average_cost_per_kg": round(cost_sum / cost_count, 4) if cost_count else 0.0,
            "top": top_mix,
        }

    if _can(session, "products") or _can(session, "dashboard"):
        products = db.scalars(
            select(Product).where(Product.tenant_id == session.tenant_id)
        ).all()
        rows: list[dict] = []
        for product in products:
            result = calculate_product_cost(db, product.id)
            finished = result.get("finished_product_delivered") or 0.0
            product_series.append(finished)
            rows.append(
                {
                    "id": product.id,
                    "product_name": result["product_name"],
                    "client_name": result["client_name"],
                    "finished_product_delivered": finished,
                    "warnings": result["warnings"],
                }
            )

        rows.sort(key=lambda row: row["finished_product_delivered"], reverse=True)
        products_summary = {
            "count": len(products),
            "top": rows[0] if rows else None,
            "top_products": rows[:4],
        }

    return {
        "raw_materials": raw_materials_summary,
        "mixes": mixes_summary,
        "products": products_summary,
        # Pre-computed numeric series for the homepage trend chart so the
        # client doesn't need full collections to draw it.
        "trend_seeds": {
            "raw_material_cost_per_kg": raw_series,
            "mix_cost_per_kg": mix_series,
            "product_finished_delivered": product_series,
        },
        "operations": operations_summary,
    }
