"""WESP-shaped analytics and feed-quality endpoints for /reports tabs."""

from __future__ import annotations

import json
from collections import defaultdict
from datetime import UTC, datetime, timedelta
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.models import ZootechComponent
from app.modules.zootech.report_models import ZootechFeedAlert, ZootechLoadingReport
from app.modules.zootech.wesp_compat_reports import _parse_payload, _parse_report_time

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 _parse_date_window(date_from: str | None, date_to: str | None) -> tuple[datetime, datetime] | None:
    if not date_from or not date_to:
        return None
    try:
        start = datetime.strptime(date_from.strip(), "%Y-%m-%d").replace(tzinfo=UTC)
        end = datetime.strptime(date_to.strip(), "%Y-%m-%d").replace(tzinfo=UTC) + timedelta(days=1)
        return start, end
    except ValueError as exc:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail={"error": True, "message": "Некорректный формат date_from/date_to (YYYY-MM-DD)"},
        ) from exc


def _loading_reports_in_range(
    enterprise_id: str, window: tuple[datetime, datetime] | None
) -> list[tuple[str, dict[str, Any]]]:
    with session_scope() as db:
        rows = list(
            db.scalars(
                select(ZootechLoadingReport).where(
                    ZootechLoadingReport.enterprise_id == enterprise_id,
                    ZootechLoadingReport.is_deleted.is_(False),
                )
            )
        )
        out: list[tuple[str, dict[str, Any]]] = []
        for row in rows:
            payload = _parse_payload(row)
            start_time = _parse_report_time(payload.get("start_time"))
            if window and start_time and not (window[0] <= start_time < window[1]):
                continue
            if window and start_time is None:
                continue
            out.append((row.id, payload))
        return out


def _component_prices(enterprise_id: str) -> dict[str, dict[str, float]]:
    with session_scope() as db:
        rows = list(
            db.scalars(
                select(ZootechComponent).where(
                    ZootechComponent.enterprise_id == enterprise_id,
                    ZootechComponent.is_deleted.is_(False),
                )
            )
        )
        by_id = {row.id: float(row.price or 0) for row in rows}
        by_name = {(row.name or "").strip().lower(): float(row.price or 0) for row in rows if row.name}
    return {"id": by_id, "name": by_name}


def _price_for(prices: dict[str, dict[str, float]], component_id: str | None, name: str) -> float:
    if component_id and component_id in prices["id"]:
        return prices["id"][component_id]
    key = (name or "").strip().lower()
    if key and key in prices["name"]:
        return prices["name"][key]
    return 0.0


@router.get("/analytics/finance")
def analytics_finance_wesp(
    enterprise_id: str = Query(...),
    date_from: str | None = Query(None),
    date_to: str | None = Query(None),
    tenant: TenantContext = Depends(require_enterprise_zootech),
):
    _require_ent(enterprise_id, tenant)
    window = _parse_date_window(date_from, date_to)
    reports = _loading_reports_in_range(enterprise_id, window)
    if not reports:
        return {
            "overloadRub": 0.0,
            "underloadRub": 0.0,
            "netRub": 0.0,
            "dominantIssue": "balanced",
            "topComponents": [],
            "reportCount": 0,
        }

    prices = _component_prices(enterprise_id)
    by_component: dict[str, dict[str, Any]] = defaultdict(
        lambda: {"name": "", "overloadRub": 0.0, "underloadRub": 0.0, "netRub": 0.0}
    )
    overload_total = 0.0
    underload_total = 0.0

    for _report_id, payload in reports:
        components = payload.get("components")
        if not isinstance(components, list):
            continue
        for comp in components:
            if not isinstance(comp, dict):
                continue
            target = float(comp.get("target_weight") or 0)
            actual = float(comp.get("actual_weight") or 0)
            if target <= 0 and actual <= 0:
                continue
            name = str(comp.get("component_name") or "—")
            price = _price_for(prices, comp.get("component_id"), name)
            dev_kg = actual - target
            dev_rub = dev_kg * price
            key = str(comp.get("component_id") or name)
            row = by_component[key]
            row["name"] = name
            row["componentId"] = comp.get("component_id")
            if dev_rub > 0:
                row["overloadRub"] += dev_rub
                overload_total += dev_rub
            elif dev_rub < 0:
                row["underloadRub"] += abs(dev_rub)
                underload_total += abs(dev_rub)
            row["netRub"] += dev_rub

    top = sorted(
        by_component.values(),
        key=lambda item: max(item["overloadRub"], item["underloadRub"]),
        reverse=True,
    )[:3]
    top_out = [
        {
            "name": item["name"],
            "componentId": item.get("componentId"),
            "overloadRub": round(item["overloadRub"], 2),
            "underloadRub": round(item["underloadRub"], 2),
            "netRub": round(item["netRub"], 2),
        }
        for item in top
        if max(item["overloadRub"], item["underloadRub"]) > 0
    ]
    net = overload_total - underload_total
    if overload_total > underload_total:
        dominant = "overload"
    elif underload_total > overload_total:
        dominant = "underload"
    else:
        dominant = "balanced"
    result = {
        "overloadRub": round(overload_total, 2),
        "underloadRub": round(underload_total, 2),
        "netRub": round(net, 2),
        "dominantIssue": dominant,
        "topComponents": top_out,
        "reportCount": len(reports),
    }
    # #region agent log
    try:
        import pathlib
        _log_path = pathlib.Path("/Users/vlad/Documents/wesp new (1)/.cursor/debug-785e22.log")
        _log_path.parent.mkdir(parents=True, exist_ok=True)
        with _log_path.open("a", encoding="utf-8") as _lf:
            _lf.write(json.dumps({"sessionId":"785e22","hypothesisId":"E","location":"wesp_compat_analytics.py:finance","message":"finance summary","data":{"date_from":date_from,"date_to":date_to,"report_count":len(reports),"underloadRub":result["underloadRub"],"top_count":len(top_out)},"timestamp":int(datetime.now(UTC).timestamp()*1000)}, ensure_ascii=False) + "\n")
    except Exception:
        pass
    # #endregion
    return result


@router.get("/analytics/plan-fact")
def analytics_plan_fact_wesp(
    enterprise_id: str = Query(...),
    date_from: str | None = Query(None),
    date_to: str | None = Query(None),
    tenant: TenantContext = Depends(require_enterprise_zootech),
):
    _require_ent(enterprise_id, tenant)
    return {"items": []}


def _parse_alert_payload_json(raw: str | None) -> dict[str, Any]:
    if not raw:
        return {}
    try:
        parsed = json.loads(raw)
    except json.JSONDecodeError:
        return {}
    return parsed if isinstance(parsed, dict) else {}


def _alert_payload(row: ZootechFeedAlert) -> dict[str, Any]:
    return _parse_alert_payload_json(row.payload_json)


def _serialize_feed_alert(
    alert_id: str,
    alert_type: str,
    severity: str,
    message: str,
    created_at: datetime | None,
    payload: dict[str, Any],
) -> dict[str, Any]:
    event_type = payload.get("event_type") or alert_type or ""
    created = payload.get("created_at") or (created_at.isoformat() if created_at else None)
    return {
        "id": alert_id,
        "eventType": event_type,
        "severity": payload.get("severity") or severity or "warning",
        "loadingReportId": payload.get("loading_report_id") or "",
        "unloadingReportId": payload.get("unloading_report_id"),
        "recipeId": payload.get("recipe_id") or "",
        "recipeName": payload.get("recipe_name") or "",
        "componentName": payload.get("component_name"),
        "groupName": payload.get("group_name"),
        "detail": payload.get("detail") or message or "",
        "deviationKg": payload.get("deviation_kg"),
        "deviationPct": payload.get("deviation_pct"),
        "costDeviationRub": payload.get("cost_deviation_rub"),
        "clientId": payload.get("client_id"),
        "createdAt": created,
        "linkKind": "report_loading",
        "linkId": payload.get("loading_report_id") or "",
        "targetKg": None,
        "actualKg": None,
        "durationSec": None,
    }


def _feed_alerts_for_range(
    enterprise_id: str,
    window: tuple[datetime, datetime] | None,
    *,
    severity: str | None = None,
    limit: int = 200,
) -> list[dict[str, Any]]:
    loading_by_id = {
        report_id: payload for report_id, payload in _loading_reports_in_range(enterprise_id, window)
    }
    alert_rows: list[tuple[str, str, str, str, datetime | None, dict[str, Any]]] = []
    with session_scope() as db:
        rows = list(
            db.scalars(
                select(ZootechFeedAlert).where(
                    ZootechFeedAlert.enterprise_id == enterprise_id,
                    ZootechFeedAlert.is_deleted.is_(False),
                )
            )
        )
        for row in rows:
            payload = _parse_alert_payload_json(row.payload_json)
            alert_rows.append(
                (row.id, row.alert_type, row.severity, row.message, row.created_at, payload)
            )
    items: list[dict[str, Any]] = []
    for alert_id, alert_type, sev, message, created_at, payload in alert_rows:
        loading_id = str(payload.get("loading_report_id") or "").strip()
        if window and loading_id and loading_id not in loading_by_id:
            continue
        item = _serialize_feed_alert(alert_id, alert_type, sev, message, created_at, payload)
        if severity and (item.get("severity") or "").lower() != severity.strip().lower():
            continue
        items.append(item)
    items.sort(key=lambda item: str(item.get("createdAt") or ""), reverse=True)
    return items[: max(1, min(limit, 500))]


@router.get("/feed-quality/alerts/summary")
def feed_quality_alerts_summary_wesp(
    enterprise_id: str = Query(...),
    date_from: str | None = Query(None),
    date_to: str | None = Query(None),
    tenant: TenantContext = Depends(require_enterprise_zootech),
):
    _require_ent(enterprise_id, tenant)
    window = _parse_date_window(date_from, date_to)
    items = _feed_alerts_for_range(enterprise_id, window, limit=500)
    by_severity = {"warning": 0, "error": 0, "info": 0}
    for item in items:
        sev = (item.get("severity") or "warning").lower()
        if sev in by_severity:
            by_severity[sev] += 1
    return {
        "total": len(items),
        "warning": by_severity["warning"],
        "error": by_severity["error"],
        "info": by_severity["info"],
    }


@router.get("/feed-quality/alerts")
def feed_quality_alerts_wesp(
    enterprise_id: str = Query(...),
    date_from: str | None = Query(None),
    date_to: str | None = Query(None),
    severity: str | None = Query(None),
    limit: int = Query(200, ge=1, le=500),
    tenant: TenantContext = Depends(require_enterprise_zootech),
):
    _require_ent(enterprise_id, tenant)
    window = _parse_date_window(date_from, date_to)
    items = _feed_alerts_for_range(enterprise_id, window, severity=severity, limit=limit)
    by_severity = {"warning": 0, "error": 0, "info": 0}
    for item in items:
        sev = (item.get("severity") or "warning").lower()
        if sev in by_severity:
            by_severity[sev] += 1
    return {"items": items, "total": len(items), "bySeverity": by_severity}
