from __future__ import annotations

from math import sqrt
from statistics import mean
from typing import Any

from services.pet_builder.action_atlas import CELL_HEIGHT, CELL_WIDTH


BASE_RUNTIME_SCALE = 0.75
MAX_ROW_AREA_CV = 0.075
MAX_ROW_AREA_RATIO = 1.20
ACTION_ROW_STABILITY_LIMITS = {
    "sleep": {
        "max_area_cv": 0.10,
        "max_area_ratio": 1.30,
    },
}
ACTION_DISPLAY_SCALES = {
    "idle": 0.929,
    "walk": 1.2,
}
SIT_RELATIVE_AREA_RANGES = {
    "idle": (0.80, 1.10),
    "walk": (0.80, 1.12),
}
RELATIVE_AREA_POSE_EXCEPTIONS = {
    "idle": {
        "min_area_ratio": 0.70,
        "min_width_ratio": 1.0,
    },
}
IDLE_NEAR_SIT_WIDTH_EXCEPTION = {
    "min_area_ratio": 0.76,
    "min_width_ratio": 0.95,
    "max_area_cv": MAX_ROW_AREA_CV,
    "max_area_max_min_ratio": MAX_ROW_AREA_RATIO,
    "max_width_max_min_ratio": 1.12,
    "max_height_max_min_ratio": 1.18,
}
IDLE_FRONT_SIT_NARROW_EXCEPTION = {
    "min_area_ratio": 0.72,
    "min_width_ratio": 0.80,
    "min_height_ratio": 0.90,
    "max_area_cv": 0.05,
    "max_area_max_min_ratio": 1.12,
    "max_width_max_min_ratio": 1.13,
    "max_height_max_min_ratio": 1.08,
}
IDLE_STANDING_SIT_AREA_EXCEPTION = {
    "min_area_ratio": 0.70,
    "min_width_ratio": 0.90,
    "max_width_ratio": 1.05,
    "min_height_ratio": 0.70,
    "max_height_ratio": 0.86,
    "max_area_cv": 0.05,
    "max_area_max_min_ratio": 1.12,
    "max_width_max_min_ratio": 1.12,
    "max_height_max_min_ratio": 1.18,
}
IDLE_COMPACT_SIT_UPPER_EXCEPTION = {
    "max_area_ratio": 1.18,
    "min_width_ratio": 1.15,
    "max_width_ratio": 1.40,
    "max_height_ratio": 0.95,
    "max_area_cv": MAX_ROW_AREA_CV,
    "max_area_max_min_ratio": MAX_ROW_AREA_RATIO,
    "max_width_max_min_ratio": 1.12,
    "max_height_max_min_ratio": 1.22,
}
WALK_COMPACT_SIT_UPPER_EXCEPTION = {
    "max_area_ratio": 1.35,
    "min_width_ratio": 1.55,
    "max_width_ratio": 1.90,
    "max_height_ratio": 0.82,
    "max_area_cv": MAX_ROW_AREA_CV,
    "max_area_max_min_ratio": MAX_ROW_AREA_RATIO,
    "max_width_max_min_ratio": 1.08,
    "max_height_max_min_ratio": 1.10,
}
ACTION_DIMENSION_STABILITY_RANGES = {
    "idle": {
        "width_max_min_ratio": 1.18,
        "height_max_min_ratio": 1.18,
    },
}
IDLE_POSE_HEIGHT_VARIATION_EXCEPTION = {
    "max_height_max_min_ratio": 1.30,
    "max_width_max_min_ratio": 1.12,
    "max_area_cv": MAX_ROW_AREA_CV,
    "max_area_max_min_ratio": MAX_ROW_AREA_RATIO,
}
IDLE_TAIL_WIDTH_VARIATION_EXCEPTION = {
    "max_width_max_min_ratio": 1.40,
    "max_height_max_min_ratio": 1.10,
    "max_area_cv": 0.09,
    "max_area_max_min_ratio": 1.30,
}
TAIL_WAG_WIDTH_VARIATION_EXCEPTION = {
    "max_width_max_min_ratio": 1.40,
    "max_height_max_min_ratio": 1.10,
    "max_area_cv": 0.11,
    "max_area_max_min_ratio": 1.35,
}


def evaluate_size_proportions(frame_qa: dict[str, Any]) -> dict[str, Any]:
    rows = []
    row_by_action = {}
    ok = True
    failure_reasons: set[str] = set()

    for source_row in frame_qa.get("rows", []):
        action = str(source_row.get("action", ""))
        frames = [frame for frame in source_row.get("frames", []) if isinstance(frame, dict)]
        metrics = _row_size_metrics(action=action, frames=frames)
        reasons: list[str] = []
        row_ok = True
        max_area_cv = _action_stability_limit(action, "max_area_cv", MAX_ROW_AREA_CV)
        max_area_ratio = _action_stability_limit(action, "max_area_ratio", MAX_ROW_AREA_RATIO)

        tail_width_exception = _tail_width_variation_exception_applies(action=action, metrics=metrics)
        if metrics["frame_count"] > 1 and metrics["area_cv"] > max_area_cv and not tail_width_exception:
            row_ok = False
            reasons.append("row_area_cv_too_high")
            failure_reasons.add("row_area_unstable")
        if metrics["frame_count"] > 1 and metrics["area_max_min_ratio"] > max_area_ratio and not tail_width_exception:
            row_ok = False
            reasons.append("row_area_max_min_ratio_too_high")
            failure_reasons.add("row_area_unstable")
        dimension_limits = ACTION_DIMENSION_STABILITY_RANGES.get(action)
        if dimension_limits and metrics["frame_count"] > 1:
            max_width_ratio = float(dimension_limits.get("width_max_min_ratio", 0.0))
            max_height_ratio = float(dimension_limits.get("height_max_min_ratio", 0.0))
            if max_width_ratio > 0 and metrics["width_max_min_ratio"] > max_width_ratio:
                if not tail_width_exception:
                    row_ok = False
                    reasons.append("row_width_max_min_ratio_too_high")
                    failure_reasons.add("row_dimension_unstable")
            if max_height_ratio > 0 and metrics["height_max_min_ratio"] > max_height_ratio:
                if not _idle_pose_height_variation_exception_applies(action=action, metrics=metrics):
                    row_ok = False
                    reasons.append("row_height_max_min_ratio_too_high")
                    failure_reasons.add("row_dimension_unstable")

        row = {
            "action": action,
            "ok": row_ok,
            "reasons": reasons,
            **metrics,
        }
        rows.append(row)
        row_by_action[action] = row
        ok = ok and row_ok

    sit_row = row_by_action.get("sit") or {}
    sit_area = sit_row.get("avg_display_area")
    sit_width = sit_row.get("avg_display_width")
    sit_height = sit_row.get("avg_display_height")
    if isinstance(sit_area, (int, float)) and sit_area > 0:
        for action, (lower, upper) in SIT_RELATIVE_AREA_RANGES.items():
            row = row_by_action.get(action)
            if not row:
                continue
            ratio = float(row.get("avg_display_area") or 0.0) / float(sit_area)
            row["relative_to_sit_area"] = ratio
            row["relative_to_sit_allowed_range"] = [lower, upper]
            if isinstance(sit_width, (int, float)) and sit_width > 0:
                row["relative_to_sit_width"] = float(row.get("avg_display_width") or 0.0) / float(sit_width)
            if isinstance(sit_height, (int, float)) and sit_height > 0:
                row["relative_to_sit_height"] = float(row.get("avg_display_height") or 0.0) / float(sit_height)
            if ratio < lower and _relative_area_pose_exception_applies(action=action, row=row, ratio=ratio):
                continue
            if ratio > upper and _relative_area_upper_exception_applies(action=action, row=row, ratio=ratio):
                continue
            if ratio < lower or ratio > upper:
                row["ok"] = False
                row["reasons"].append("relative_to_sit_area_out_of_range")
                failure_reasons.add("relative_action_size_out_of_range")
                ok = False

    return {
        "ok": ok,
        "failure_reasons": sorted(failure_reasons),
        "policy": {
            "base_runtime_scale": BASE_RUNTIME_SCALE,
            "action_display_scales": ACTION_DISPLAY_SCALES,
            "max_row_area_cv": MAX_ROW_AREA_CV,
            "max_row_area_ratio": MAX_ROW_AREA_RATIO,
            "action_row_stability_limits": ACTION_ROW_STABILITY_LIMITS,
            "sit_relative_area_ranges": {
                action: [lower, upper]
                for action, (lower, upper) in SIT_RELATIVE_AREA_RANGES.items()
            },
            "relative_area_pose_exceptions": RELATIVE_AREA_POSE_EXCEPTIONS,
            "idle_near_sit_width_exception": IDLE_NEAR_SIT_WIDTH_EXCEPTION,
            "idle_front_sit_narrow_exception": IDLE_FRONT_SIT_NARROW_EXCEPTION,
            "idle_standing_sit_area_exception": IDLE_STANDING_SIT_AREA_EXCEPTION,
            "idle_compact_sit_upper_exception": IDLE_COMPACT_SIT_UPPER_EXCEPTION,
            "walk_compact_sit_upper_exception": WALK_COMPACT_SIT_UPPER_EXCEPTION,
            "action_dimension_stability_ranges": ACTION_DIMENSION_STABILITY_RANGES,
            "idle_pose_height_variation_exception": IDLE_POSE_HEIGHT_VARIATION_EXCEPTION,
            "idle_tail_width_variation_exception": IDLE_TAIL_WIDTH_VARIATION_EXCEPTION,
            "tail_wag_width_variation_exception": TAIL_WAG_WIDTH_VARIATION_EXCEPTION,
        },
        "rows": rows,
    }


def _action_stability_limit(action: str, key: str, fallback: float) -> float:
    action_limits = ACTION_ROW_STABILITY_LIMITS.get(action, {})
    value = action_limits.get(key, fallback)
    return float(value)


def _idle_pose_height_variation_exception_applies(*, action: str, metrics: dict[str, Any]) -> bool:
    if action != "idle":
        return False
    return (
        metrics["height_max_min_ratio"] <= float(IDLE_POSE_HEIGHT_VARIATION_EXCEPTION["max_height_max_min_ratio"])
        and metrics["width_max_min_ratio"] <= float(IDLE_POSE_HEIGHT_VARIATION_EXCEPTION["max_width_max_min_ratio"])
        and metrics["area_cv"] <= float(IDLE_POSE_HEIGHT_VARIATION_EXCEPTION["max_area_cv"])
        and metrics["area_max_min_ratio"] <= float(IDLE_POSE_HEIGHT_VARIATION_EXCEPTION["max_area_max_min_ratio"])
    )


def _idle_tail_width_variation_exception_applies(*, action: str, metrics: dict[str, Any]) -> bool:
    if action != "idle":
        return False
    return (
        metrics["width_max_min_ratio"] <= float(IDLE_TAIL_WIDTH_VARIATION_EXCEPTION["max_width_max_min_ratio"])
        and metrics["height_max_min_ratio"] <= float(IDLE_TAIL_WIDTH_VARIATION_EXCEPTION["max_height_max_min_ratio"])
        and metrics["area_cv"] <= float(IDLE_TAIL_WIDTH_VARIATION_EXCEPTION["max_area_cv"])
        and metrics["area_max_min_ratio"] <= float(IDLE_TAIL_WIDTH_VARIATION_EXCEPTION["max_area_max_min_ratio"])
    )


def _tail_wag_width_variation_exception_applies(*, action: str, metrics: dict[str, Any]) -> bool:
    if action != "tail_wag":
        return False
    return (
        metrics["width_max_min_ratio"] <= float(TAIL_WAG_WIDTH_VARIATION_EXCEPTION["max_width_max_min_ratio"])
        and metrics["height_max_min_ratio"] <= float(TAIL_WAG_WIDTH_VARIATION_EXCEPTION["max_height_max_min_ratio"])
        and metrics["area_cv"] <= float(TAIL_WAG_WIDTH_VARIATION_EXCEPTION["max_area_cv"])
        and metrics["area_max_min_ratio"] <= float(TAIL_WAG_WIDTH_VARIATION_EXCEPTION["max_area_max_min_ratio"])
    )


def _tail_width_variation_exception_applies(*, action: str, metrics: dict[str, Any]) -> bool:
    return (
        _idle_tail_width_variation_exception_applies(action=action, metrics=metrics)
        or _tail_wag_width_variation_exception_applies(action=action, metrics=metrics)
    )


def _relative_area_pose_exception_applies(*, action: str, row: dict[str, Any], ratio: float) -> bool:
    exception = RELATIVE_AREA_POSE_EXCEPTIONS.get(action)
    if exception:
        min_area_ratio = float(exception.get("min_area_ratio", 0.0))
        min_width_ratio = float(exception.get("min_width_ratio", 0.0))
        width_ratio = row.get("relative_to_sit_width")
        if (
            ratio >= min_area_ratio
            and isinstance(width_ratio, (int, float))
            and float(width_ratio) >= min_width_ratio
        ):
            return True
    return (
        _idle_near_sit_width_exception_applies(action=action, row=row, ratio=ratio)
        or _idle_front_sit_narrow_exception_applies(action=action, row=row, ratio=ratio)
        or _idle_standing_sit_area_exception_applies(action=action, row=row, ratio=ratio)
    )


def _idle_near_sit_width_exception_applies(*, action: str, row: dict[str, Any], ratio: float) -> bool:
    if action != "idle":
        return False
    width_ratio = row.get("relative_to_sit_width")
    return (
        ratio >= float(IDLE_NEAR_SIT_WIDTH_EXCEPTION["min_area_ratio"])
        and isinstance(width_ratio, (int, float))
        and float(width_ratio) >= float(IDLE_NEAR_SIT_WIDTH_EXCEPTION["min_width_ratio"])
        and float(row.get("area_cv") or 0.0) <= float(IDLE_NEAR_SIT_WIDTH_EXCEPTION["max_area_cv"])
        and float(row.get("area_max_min_ratio") or 0.0)
        <= float(IDLE_NEAR_SIT_WIDTH_EXCEPTION["max_area_max_min_ratio"])
        and float(row.get("width_max_min_ratio") or 0.0)
        <= float(IDLE_NEAR_SIT_WIDTH_EXCEPTION["max_width_max_min_ratio"])
        and float(row.get("height_max_min_ratio") or 0.0)
        <= float(IDLE_NEAR_SIT_WIDTH_EXCEPTION["max_height_max_min_ratio"])
    )


def _idle_front_sit_narrow_exception_applies(*, action: str, row: dict[str, Any], ratio: float) -> bool:
    if action != "idle":
        return False
    width_ratio = row.get("relative_to_sit_width")
    height_ratio = row.get("relative_to_sit_height")
    return (
        ratio >= float(IDLE_FRONT_SIT_NARROW_EXCEPTION["min_area_ratio"])
        and isinstance(width_ratio, (int, float))
        and float(width_ratio) >= float(IDLE_FRONT_SIT_NARROW_EXCEPTION["min_width_ratio"])
        and isinstance(height_ratio, (int, float))
        and float(height_ratio) >= float(IDLE_FRONT_SIT_NARROW_EXCEPTION["min_height_ratio"])
        and float(row.get("area_cv") or 0.0) <= float(IDLE_FRONT_SIT_NARROW_EXCEPTION["max_area_cv"])
        and float(row.get("area_max_min_ratio") or 0.0)
        <= float(IDLE_FRONT_SIT_NARROW_EXCEPTION["max_area_max_min_ratio"])
        and float(row.get("width_max_min_ratio") or 0.0)
        <= float(IDLE_FRONT_SIT_NARROW_EXCEPTION["max_width_max_min_ratio"])
        and float(row.get("height_max_min_ratio") or 0.0)
        <= float(IDLE_FRONT_SIT_NARROW_EXCEPTION["max_height_max_min_ratio"])
    )


def _idle_standing_sit_area_exception_applies(*, action: str, row: dict[str, Any], ratio: float) -> bool:
    if action != "idle":
        return False
    width_ratio = row.get("relative_to_sit_width")
    height_ratio = row.get("relative_to_sit_height")
    return (
        ratio >= float(IDLE_STANDING_SIT_AREA_EXCEPTION["min_area_ratio"])
        and isinstance(width_ratio, (int, float))
        and float(width_ratio) >= float(IDLE_STANDING_SIT_AREA_EXCEPTION["min_width_ratio"])
        and float(width_ratio) <= float(IDLE_STANDING_SIT_AREA_EXCEPTION["max_width_ratio"])
        and isinstance(height_ratio, (int, float))
        and float(height_ratio) >= float(IDLE_STANDING_SIT_AREA_EXCEPTION["min_height_ratio"])
        and float(height_ratio) <= float(IDLE_STANDING_SIT_AREA_EXCEPTION["max_height_ratio"])
        and float(row.get("area_cv") or 0.0) <= float(IDLE_STANDING_SIT_AREA_EXCEPTION["max_area_cv"])
        and float(row.get("area_max_min_ratio") or 0.0)
        <= float(IDLE_STANDING_SIT_AREA_EXCEPTION["max_area_max_min_ratio"])
        and float(row.get("width_max_min_ratio") or 0.0)
        <= float(IDLE_STANDING_SIT_AREA_EXCEPTION["max_width_max_min_ratio"])
        and float(row.get("height_max_min_ratio") or 0.0)
        <= float(IDLE_STANDING_SIT_AREA_EXCEPTION["max_height_max_min_ratio"])
    )


def _relative_area_upper_exception_applies(*, action: str, row: dict[str, Any], ratio: float) -> bool:
    if action == "idle":
        return _compact_sit_upper_exception_applies(
            row=row,
            ratio=ratio,
            exception=IDLE_COMPACT_SIT_UPPER_EXCEPTION,
        )
    if action == "walk":
        return _compact_sit_upper_exception_applies(
            row=row,
            ratio=ratio,
            exception=WALK_COMPACT_SIT_UPPER_EXCEPTION,
        )
    return False


def _compact_sit_upper_exception_applies(
    *,
    row: dict[str, Any],
    ratio: float,
    exception: dict[str, float],
) -> bool:
    width_ratio = row.get("relative_to_sit_width")
    height_ratio = row.get("relative_to_sit_height")
    return (
        ratio <= float(exception["max_area_ratio"])
        and isinstance(width_ratio, (int, float))
        and float(width_ratio) >= float(exception["min_width_ratio"])
        and float(width_ratio) <= float(exception["max_width_ratio"])
        and isinstance(height_ratio, (int, float))
        and float(height_ratio) <= float(exception["max_height_ratio"])
        and float(row.get("area_cv") or 0.0) <= float(exception["max_area_cv"])
        and float(row.get("area_max_min_ratio") or 0.0) <= float(exception["max_area_max_min_ratio"])
        and float(row.get("width_max_min_ratio") or 0.0) <= float(exception["max_width_max_min_ratio"])
        and float(row.get("height_max_min_ratio") or 0.0) <= float(exception["max_height_max_min_ratio"])
    )


def _row_size_metrics(*, action: str, frames: list[dict[str, Any]]) -> dict[str, Any]:
    display_sizes = []
    for frame in frames:
        width, height, metric_source = _frame_metric_size(frame)
        if width <= 0 or height <= 0:
            continue
        if metric_source == "source_bbox":
            fitted_width, fitted_height = _fit_source_bbox_size_to_cell(width=width, height=height)
        else:
            fitted_width, fitted_height = float(width), float(height)
        runtime_scale = BASE_RUNTIME_SCALE * float(ACTION_DISPLAY_SCALES.get(action, 1.0))
        display_width = fitted_width * runtime_scale
        display_height = fitted_height * runtime_scale
        display_sizes.append(
            {
                "frame": frame.get("frame"),
                "metric_source": metric_source,
                "display_width": display_width,
                "display_height": display_height,
                "display_area": display_width * display_height,
            }
        )
    areas = [float(item["display_area"]) for item in display_sizes]
    widths = [float(item["display_width"]) for item in display_sizes]
    heights = [float(item["display_height"]) for item in display_sizes]
    avg_area = mean(areas) if areas else 0.0
    area_std = _population_std(areas) if areas else 0.0
    width_std = _population_std(widths) if widths else 0.0
    height_std = _population_std(heights) if heights else 0.0
    return {
        "frame_count": len(display_sizes),
        "display_frames": [
            {
                "frame": item["frame"],
                "metric_source": item["metric_source"],
                "display_width": round(float(item["display_width"]), 2),
                "display_height": round(float(item["display_height"]), 2),
                "display_area": round(float(item["display_area"]), 2),
            }
            for item in display_sizes
        ],
        "avg_display_width": round(mean(widths), 2) if widths else 0.0,
        "avg_display_height": round(mean(heights), 2) if heights else 0.0,
        "avg_display_area": round(avg_area, 2),
        "area_cv": round(area_std / avg_area, 6) if avg_area else 0.0,
        "area_max_min_ratio": round(max(areas) / max(min(areas), 1.0), 6) if areas else 0.0,
        "width_cv": round(width_std / mean(widths), 6) if widths and mean(widths) else 0.0,
        "height_cv": round(height_std / mean(heights), 6) if heights and mean(heights) else 0.0,
        "width_max_min_ratio": round(max(widths) / max(min(widths), 1.0), 6) if widths else 0.0,
        "height_max_min_ratio": round(max(heights) / max(min(heights), 1.0), 6) if heights else 0.0,
    }


def _frame_metric_size(frame: dict[str, Any]) -> tuple[int, int, str]:
    fitted_bbox = frame.get("fitted_bbox")
    if isinstance(fitted_bbox, list) and len(fitted_bbox) == 4:
        return (
            max(0, int(fitted_bbox[2]) - int(fitted_bbox[0]) + 1),
            max(0, int(fitted_bbox[3]) - int(fitted_bbox[1]) + 1),
            "fitted_bbox",
        )
    fitted_width = frame.get("fitted_width")
    fitted_height = frame.get("fitted_height")
    if isinstance(fitted_width, (int, float)) and isinstance(fitted_height, (int, float)):
        return max(0, int(fitted_width)), max(0, int(fitted_height)), "fitted_dimensions"
    bbox = frame.get("source_bbox")
    if isinstance(bbox, list) and len(bbox) == 4:
        return (
            max(0, int(bbox[2]) - int(bbox[0]) + 1),
            max(0, int(bbox[3]) - int(bbox[1]) + 1),
            "source_bbox",
        )
    return 0, 0, "missing"


def _fit_source_bbox_size_to_cell(*, width: int, height: int) -> tuple[float, float]:
    max_width = int(CELL_WIDTH * 0.84)
    max_height = int(CELL_HEIGHT * 0.84)
    scale = min(max_width / max(width, 1), max_height / max(height, 1), 1.0)
    return width * scale, height * scale


def _population_std(values: list[float]) -> float:
    if not values:
        return 0.0
    avg = mean(values)
    return sqrt(sum((value - avg) ** 2 for value in values) / len(values))
