"""WESP-shaped HTTP API for copied static UI (orchestrator zootech catalog)."""

from __future__ import annotations

import json
from typing import Any

from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import select

from app.core.database import session_scope
from app.modules.sync.tenant import TenantContext, require_enterprise_zootech
from app.modules.zootech.catalog_apply import load_catalog_row
from app.modules.zootech.catalog_models import (
    ZootechFeedDispenser,
    ZootechFeedingPeriod,
    ZootechPeriodRecipe,
    ZootechUnloadingGroup,
)
from app.modules.zootech.models import ZootechComponent, ZootechIngredient, ZootechRecipe

router = APIRouter()


def _require_ent(enterprise_id: str, tenant: TenantContext) -> None:
    if tenant.enterprise_id != enterprise_id:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="ENTERPRISE_FORBIDDEN")


def _component_wesp(row: ZootechComponent) -> dict[str, Any]:
    return {
        "id": row.id,
        "name": row.name,
        "type": row.type,
        "is_active": row.is_active,
        "dryMatter": row.dry_matter,
        "protein": row.protein,
        "energy": row.energy,
        "price": row.price,
        "externalNo": row.external_no,
        "version": row.version,
        "content_hash": row.content_hash,
    }


def _recipe_short(row: ZootechRecipe) -> dict[str, Any]:
    return {
        "id": row.id,
        "name": row.name,
        "heads_count": row.heads_per_trip,
        "mixing_time": row.mixing_time,
        "trip_percent": row.trip_percent,
    }


def _ingredient_wesp(row: ZootechIngredient, comp_name: str | None = None) -> dict[str, Any]:
    wph = float(row.weight_per_head or 0)
    dm_pct = float(row.dry_matter or 0)
    dm_ph = float(row.dry_matter_per_head or 0)
    if not dm_ph and wph > 0 and dm_pct > 0:
        dm_ph = wph * (dm_pct / 100.0)
    name = (row.name or "").strip() or (comp_name or "") or "—"
    return {
        "id": row.id,
        "name": name,
        "weightPerHead": wph,
        "weight_per_head": wph,
        "amount": float(row.amount or 0),
        "dry_matter": dm_pct,
        "dry_matter_per_head": dm_ph,
        "order": int(row.order or 0),
        "component_id": row.component_id,
        "version": row.version,
    }


def _unloading_group_wesp(row: ZootechUnloadingGroup) -> dict[str, Any]:
    extra: dict[str, Any] = {}
    if row.payload_json:
        try:
            extra = json.loads(row.payload_json)
        except json.JSONDecodeError:
            pass
    distribution_type = str(extra.get("distribution_type") or extra.get("distributionType") or "percent")
    value = float(extra.get("value") or 0)
    weight = float(extra.get("weight") or 0)
    order = int(extra.get("order") or 0)
    base = {
        "id": row.id,
        "name": row.name,
        "distributionType": distribution_type,
        "distribution_type": distribution_type,
        "value": value,
        "weight": weight,
        "order": order,
        "version": row.version,
    }
    for key in ("created_at", "updated_at", "created_by", "updated_by"):
        if key in extra:
            base[key] = extra[key]
    return base


def _serialize_recipe_wesp(db, enterprise_id: str, recipe: ZootechRecipe) -> dict[str, Any]:
    ingredients = list(
        db.scalars(
            select(ZootechIngredient)
            .where(
                ZootechIngredient.enterprise_id == enterprise_id,
                ZootechIngredient.recipe_id == recipe.id,
                ZootechIngredient.is_deleted.is_(False),
            )
            .order_by(ZootechIngredient.order.asc())
        )
    )
    comp_ids = [i.component_id for i in ingredients if i.component_id]
    comp_names: dict[str, str] = {}
    if comp_ids:
        for comp in db.scalars(
            select(ZootechComponent).where(
                ZootechComponent.enterprise_id == enterprise_id,
                ZootechComponent.id.in_(comp_ids),
            )
        ):
            comp_names[comp.id] = comp.name

    groups = list(
        db.scalars(
            select(ZootechUnloadingGroup)
            .where(
                ZootechUnloadingGroup.enterprise_id == enterprise_id,
                ZootechUnloadingGroup.recipe_id == recipe.id,
                ZootechUnloadingGroup.is_deleted.is_(False),
            )
        )
    )
    groups.sort(key=lambda g: int((_unloading_group_wesp(g).get("order") or 0)))

    unloading_groups = [_unloading_group_wesp(g) for g in groups]
    return {
        "id": recipe.id,
        "name": recipe.name,
        "headsPerTrip": recipe.heads_per_trip,
        "mixingTime": recipe.mixing_time,
        "tripPercent": recipe.trip_percent,
        "heads_count": recipe.heads_per_trip,
        "mixing_time": recipe.mixing_time,
        "trip_percent": recipe.trip_percent,
        "dryMatterLocked": recipe.dry_matter_locked,
        "dry_matter_locked": recipe.dry_matter_locked,
        "unloading_link_broken": recipe.unloading_link_broken,
        "unloadingLinkBroken": recipe.unloading_link_broken,
        "target_component_id": recipe.target_component_id,
        "version": recipe.version,
        "ingredients": [_ingredient_wesp(i, comp_names.get(i.component_id or "")) for i in ingredients],
        "unloadingGroups": unloading_groups,
        "unloading_groups": unloading_groups,
    }


def _dispenser_wesp(row: ZootechFeedDispenser, periods: list[dict] | None = None) -> dict[str, Any]:
    extra: dict[str, Any] = {}
    if row.payload_json:
        try:
            extra = json.loads(row.payload_json)
        except json.JSONDecodeError:
            pass
    return {
        "id": row.id,
        "name": row.name,
        "version": row.version,
        "content_hash": row.content_hash,
        "periods": periods or [],
        "hasSkipToday": False,
        **{k: v for k, v in extra.items() if k not in ("id", "name")},
    }


@router.get("/components/ping")
def components_ping():
    return {"status": "ok"}


@router.get("/components")
def wesp_list_components(
    enterprise_id: str = Query(...),
    tenant: TenantContext = Depends(require_enterprise_zootech),
    limit: int = Query(1000),
    offset: int = Query(0),
):
    _require_ent(enterprise_id, tenant)
    with session_scope() as db:
        rows = list(
            db.scalars(
                select(ZootechComponent)
                .where(
                    ZootechComponent.enterprise_id == enterprise_id,
                    ZootechComponent.is_deleted.is_(False),
                    ZootechComponent.is_active.is_(True),
                )
                .offset(offset)
                .limit(limit)
            )
        )
        return [_component_wesp(r) for r in rows]


@router.get("/components/{component_id}")
def wesp_get_component(
    component_id: str,
    enterprise_id: str = Query(...),
    tenant: TenantContext = Depends(require_enterprise_zootech),
):
    _require_ent(enterprise_id, tenant)
    row = load_catalog_row(enterprise_id, "component", component_id)
    if not row:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="NOT_FOUND")
    return {
        "id": row["id"],
        "name": row["name"],
        "type": row.get("type", ""),
        "is_active": row.get("is_active", True),
        "dryMatter": row.get("dry_matter", 0),
        "protein": row.get("protein", 0),
        "energy": row.get("energy", 0),
        "price": row.get("price", 0),
        "externalNo": row.get("external_no"),
        "version": row.get("version", 1),
        "content_hash": row.get("content_hash", ""),
    }


@router.get("/feed_dispensers/ping")
def feed_dispensers_ping():
    return {"status": "ok"}


@router.get("/feed_dispensers/names")
def feed_dispenser_names(
    enterprise_id: str = Query(...),
    tenant: TenantContext = Depends(require_enterprise_zootech),
):
    _require_ent(enterprise_id, tenant)
    with session_scope() as db:
        rows = list(
            db.scalars(
                select(ZootechFeedDispenser)
                .where(
                    ZootechFeedDispenser.enterprise_id == enterprise_id,
                    ZootechFeedDispenser.is_deleted.is_(False),
                )
                .order_by(ZootechFeedDispenser.name.asc())
            )
        )
        seen: set[str] = set()
        names: list[str] = []
        for r in rows:
            if r.name and r.name not in seen:
                seen.add(r.name)
                names.append(r.name)
        return names


@router.get("/feed_dispensers")
def list_feed_dispensers(
    enterprise_id: str = Query(...),
    tenant: TenantContext = Depends(require_enterprise_zootech),
    limit: int = Query(100),
    offset: int = Query(0),
):
    _require_ent(enterprise_id, tenant)
    with session_scope() as db:
        dispensers = list(
            db.scalars(
                select(ZootechFeedDispenser)
                .where(
                    ZootechFeedDispenser.enterprise_id == enterprise_id,
                    ZootechFeedDispenser.is_deleted.is_(False),
                )
                .order_by(ZootechFeedDispenser.created_at.desc())
                .offset(offset)
                .limit(limit)
            )
        )
        result = []
        for d in dispensers:
            periods = list(
                db.scalars(
                    select(ZootechFeedingPeriod).where(
                        ZootechFeedingPeriod.enterprise_id == enterprise_id,
                        ZootechFeedingPeriod.dispenser_id == d.id,
                        ZootechFeedingPeriod.is_deleted.is_(False),
                    )
                )
            )
            period_payload = [{"id": p.id, "name": p.name} for p in periods]
            result.append(_dispenser_wesp(d, period_payload))
        return result


@router.get("/feed_dispensers/{dispenser_id}/periods")
def dispenser_periods(
    dispenser_id: str,
    enterprise_id: str = Query(...),
    tenant: TenantContext = Depends(require_enterprise_zootech),
):
    _require_ent(enterprise_id, tenant)
    with session_scope() as db:
        periods = list(
            db.scalars(
                select(ZootechFeedingPeriod).where(
                    ZootechFeedingPeriod.enterprise_id == enterprise_id,
                    ZootechFeedingPeriod.dispenser_id == dispenser_id,
                    ZootechFeedingPeriod.is_deleted.is_(False),
                )
            )
        )
        return [{"id": p.id, "name": p.name, "dispenser_id": p.dispenser_id} for p in periods]


def _recipes_for_period(db, enterprise_id: str, period_id: str) -> list[dict]:
    links = list(
        db.scalars(
            select(ZootechPeriodRecipe)
            .where(
                ZootechPeriodRecipe.enterprise_id == enterprise_id,
                ZootechPeriodRecipe.period_id == period_id,
                ZootechPeriodRecipe.is_deleted.is_(False),
            )
            .order_by(ZootechPeriodRecipe.order.asc())
        )
    )
    out: list[dict] = []
    for link in links:
        recipe = db.scalar(
            select(ZootechRecipe).where(
                ZootechRecipe.enterprise_id == enterprise_id,
                ZootechRecipe.id == link.recipe_id,
                ZootechRecipe.is_deleted.is_(False),
            )
        )
        if recipe:
            out.append(_recipe_short(recipe))
    return out


@router.get("/feed_dispensers/{dispenser_id}/recipes")
def dispenser_recipes(
    dispenser_id: str,
    enterprise_id: str = Query(...),
    tenant: TenantContext = Depends(require_enterprise_zootech),
):
    _require_ent(enterprise_id, tenant)
    with session_scope() as db:
        periods = list(
            db.scalars(
                select(ZootechFeedingPeriod).where(
                    ZootechFeedingPeriod.enterprise_id == enterprise_id,
                    ZootechFeedingPeriod.dispenser_id == dispenser_id,
                    ZootechFeedingPeriod.is_deleted.is_(False),
                )
            )
        )
        merged: dict[str, dict] = {}
        for p in periods:
            for r in _recipes_for_period(db, enterprise_id, p.id):
                merged[r["id"]] = r
        return list(merged.values())


@router.get("/periods/{period_id}/recipes")
def period_recipes(
    period_id: str,
    enterprise_id: str = Query(...),
    tenant: TenantContext = Depends(require_enterprise_zootech),
):
    _require_ent(enterprise_id, tenant)
    with session_scope() as db:
        period = db.scalar(
            select(ZootechFeedingPeriod).where(
                ZootechFeedingPeriod.enterprise_id == enterprise_id,
                ZootechFeedingPeriod.id == period_id,
                ZootechFeedingPeriod.is_deleted.is_(False),
            )
        )
        if not period:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="NOT_FOUND")
        return _recipes_for_period(db, enterprise_id, period_id)


@router.get("/recipes/{recipe_id}")
def wesp_get_recipe(
    recipe_id: str,
    enterprise_id: str = Query(...),
    tenant: TenantContext = Depends(require_enterprise_zootech),
    date: str | None = Query(None),
):
    _require_ent(enterprise_id, tenant)
    with session_scope() as db:
        recipe = db.scalar(
            select(ZootechRecipe).where(
                ZootechRecipe.enterprise_id == enterprise_id,
                ZootechRecipe.id == recipe_id,
                ZootechRecipe.is_deleted.is_(False),
            )
        )
        if not recipe:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="NOT_FOUND")
        return _serialize_recipe_wesp(db, enterprise_id, recipe)


@router.post("/recipes/calculate")
def wesp_calculate_recipe(body: dict):
    from app.modules.zootech.wesp_recipe_write import RecipeWriteError, calculate_recipe_wesp, recipe_write_http_error

    try:
        return calculate_recipe_wesp(body)
    except RecipeWriteError as exc:
        raise recipe_write_http_error(exc) from exc


@router.put("/recipes/{recipe_id}")
def wesp_update_recipe(
    recipe_id: str,
    body: dict,
    enterprise_id: str = Query(...),
    tenant: TenantContext = Depends(require_enterprise_zootech),
):
    from app.modules.zootech.wesp_recipe_write import RecipeWriteError, recipe_write_http_error, update_recipe_wesp

    _require_ent(enterprise_id, tenant)
    try:
        return update_recipe_wesp(enterprise_id, recipe_id, body)
    except RecipeWriteError as exc:
        raise recipe_write_http_error(exc) from exc
