import hashlib
import json
import re
import subprocess
import time
import urllib.parse


def kubernetes_retention_config(config):
    if not isinstance(config, dict):
        raise RuntimeError("required YAML kubernetesObjectRetention policy is missing")
    if not isinstance(config.get("enabled"), bool):
        raise RuntimeError("kubernetesObjectRetention.enabled must be a YAML boolean")
    required_strings = [
        "configId",
        "kubeconfigPath",
        "namespace",
        "resource",
        "listMode",
        "countPath",
        "terminalConditionType",
        "propagationPolicy",
    ]
    for key in required_strings:
        value = config.get(key)
        if not isinstance(value, str) or not value:
            raise RuntimeError("kubernetesObjectRetention.%s must be a non-empty YAML string" % key)
    if not config.get("kubeconfigPath").startswith("/"):
        raise RuntimeError("kubernetesObjectRetention.kubeconfigPath must be absolute")
    if not re.match(r"^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", config.get("namespace")):
        raise RuntimeError("kubernetesObjectRetention.namespace is invalid")
    if config.get("listMode") != "server-table":
        raise RuntimeError("kubernetesObjectRetention.listMode must be server-table")
    if "{namespace}" not in config.get("countPath") or not config.get("countPath").startswith("/apis/"):
        raise RuntimeError("kubernetesObjectRetention.countPath must be a namespaced API path template")
    if config.get("propagationPolicy") not in ["Background", "Foreground", "Orphan"]:
        raise RuntimeError("kubernetesObjectRetention.propagationPolicy is invalid")
    positive_integers = [
        "minAgeHours",
        "keepLatestPerGroup",
        "manualMaxDeletePerRun",
        "automaticMaxDeletePerRun",
        "previewLimit",
        "scanLimit",
        "requestTimeoutSeconds",
        "deleteBatchSize",
        "deleteTimeoutSeconds",
    ]
    for key in positive_integers:
        value = config.get(key)
        if isinstance(value, bool) or not isinstance(value, int) or value <= 0:
            raise RuntimeError("kubernetesObjectRetention.%s must be a positive YAML integer" % key)
    settle_seconds = config.get("postDeleteSettleSeconds")
    if isinstance(settle_seconds, bool) or not isinstance(settle_seconds, int) or settle_seconds < 0:
        raise RuntimeError("kubernetesObjectRetention.postDeleteSettleSeconds must be a non-negative YAML integer")
    terminal_statuses = config.get("terminalStatuses")
    if not isinstance(terminal_statuses, list) or not terminal_statuses or any(
        not isinstance(item, str) or not item for item in terminal_statuses
    ):
        raise RuntimeError("kubernetesObjectRetention.terminalStatuses must be a non-empty YAML string list")
    selectors = config.get("groupSelectors")
    if not isinstance(selectors, list) or not selectors:
        raise RuntimeError("kubernetesObjectRetention.groupSelectors must be a non-empty YAML list")
    selector_ids = set()
    for selector in selectors:
        if not isinstance(selector, dict):
            raise RuntimeError("kubernetesObjectRetention.groupSelectors items must be YAML objects")
        selector_id = selector.get("id")
        labels = selector.get("groupByLabels")
        if not isinstance(selector_id, str) or not re.match(r"^[a-z0-9][a-z0-9-]{0,63}$", selector_id):
            raise RuntimeError("kubernetesObjectRetention group selector id is invalid")
        if selector_id in selector_ids:
            raise RuntimeError("kubernetesObjectRetention group selector ids must be unique")
        selector_ids.add(selector_id)
        if not isinstance(labels, list) or not labels or any(
            not isinstance(item, str) or not item or len(item) > 253 for item in labels
        ):
            raise RuntimeError("kubernetesObjectRetention groupByLabels must be a non-empty YAML string list")
    return config


def kubernetes_retention_configs(config):
    if not isinstance(config, dict):
        raise RuntimeError("required YAML kubernetesObjectRetention policy set is missing")
    enabled = config.get("enabled")
    if not isinstance(enabled, bool):
        raise RuntimeError("kubernetesObjectRetention.enabled must be a YAML boolean")
    aggregate_preview_limit = config.get("aggregatePreviewLimit")
    if isinstance(aggregate_preview_limit, bool) or not isinstance(aggregate_preview_limit, int) or aggregate_preview_limit <= 0:
        raise RuntimeError("kubernetesObjectRetention.aggregatePreviewLimit must be a positive YAML integer")
    defaults = config.get("defaults")
    policies = config.get("policies")
    if not isinstance(defaults, dict):
        raise RuntimeError("kubernetesObjectRetention.defaults must be a YAML object")
    if not isinstance(policies, list) or not policies:
        raise RuntimeError("kubernetesObjectRetention.policies must be a non-empty YAML list")
    resolved = []
    config_ids = set()
    for index, policy in enumerate(policies):
        if not isinstance(policy, dict):
            raise RuntimeError("kubernetesObjectRetention.policies[%s] must be a YAML object" % index)
        if "enabled" in policy:
            raise RuntimeError("kubernetesObjectRetention.policies[%s].enabled is not allowed; use the root switch" % index)
        merged = dict(defaults)
        merged.update(policy)
        merged["enabled"] = enabled
        validated = kubernetes_retention_config(merged)
        config_id = validated.get("configId")
        if config_id in config_ids:
            raise RuntimeError("kubernetesObjectRetention policy configId values must be unique")
        config_ids.add(config_id)
        resolved.append(validated)
    return resolved


def kubernetes_retention_command(config, args, timeout_key):
    started = time.time()
    timeout = int(config.get(timeout_key))
    try:
        process = subprocess.run(
            args,
            text=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            timeout=timeout,
        )
        return {
            "exitCode": process.returncode,
            "stdout": process.stdout,
            "stderr": process.stderr,
            "timedOut": False,
            "elapsedMs": int((time.time() - started) * 1000),
        }
    except subprocess.TimeoutExpired as exc:
        stdout = exc.stdout.decode("utf-8", errors="replace") if isinstance(exc.stdout, bytes) else (exc.stdout or "")
        stderr = exc.stderr.decode("utf-8", errors="replace") if isinstance(exc.stderr, bytes) else (exc.stderr or "")
        return {
            "exitCode": 124,
            "stdout": stdout,
            "stderr": stderr,
            "timedOut": True,
            "elapsedMs": int((time.time() - started) * 1000),
        }


def kubernetes_retention_command_summary(result):
    return {
        "exitCode": result.get("exitCode"),
        "timedOut": result.get("timedOut") is True,
        "elapsedMs": result.get("elapsedMs"),
        "stdoutTail": str(result.get("stdout") or "")[-800:],
        "stderrTail": str(result.get("stderr") or "")[-800:],
    }


def kubernetes_retention_duration_hours(value):
    if not isinstance(value, str) or not value or value in ["<none>", "<unknown>"]:
        return None
    matches = list(re.finditer(r"([0-9]+)([ysmhd])", value))
    if not matches or "".join(match.group(0) for match in matches) != value:
        return None
    multipliers = {"y": 8760.0, "s": 1.0 / 3600.0, "m": 1.0 / 60.0, "h": 1.0, "d": 24.0}
    return sum(int(match.group(1)) * multipliers[match.group(2)] for match in matches)


def kubernetes_retention_group(labels, selectors):
    for selector in selectors:
        keys = selector.get("groupByLabels") or []
        values = [labels.get(key) for key in keys]
        if all(isinstance(value, str) and value for value in values):
            return {
                "selectorId": selector.get("id"),
                "key": "%s:%s" % (selector.get("id"), "|".join(values)),
                "labels": {key: labels.get(key) for key in keys},
            }
    return None


def kubernetes_retention_row(item, config, now_timestamp):
    labels = item.get("labels") or {}
    terminal_status = str(item.get("terminalStatus") or "")
    terminal = terminal_status in set(config.get("terminalStatuses") or [])
    group = kubernetes_retention_group(labels, config.get("groupSelectors") or [])
    age_hours = kubernetes_retention_duration_hours(item.get("age"))
    return {
        "name": item.get("name"),
        "createdAt": None,
        "createdTimestamp": now_timestamp - age_hours * 3600.0 if age_hours is not None else None,
        "age": item.get("age"),
        "ageHours": round(age_hours, 2) if age_hours is not None else None,
        "terminal": terminal,
        "terminalStatus": terminal_status or None,
        "terminalReason": item.get("terminalReason"),
        "group": group,
    }


def kubernetes_retention_list(config):
    scan_limit = int(config.get("scanLimit"))
    result = kubernetes_retention_command(config, [
        "kubectl",
        "--kubeconfig", config.get("kubeconfigPath"),
        "--namespace", config.get("namespace"),
        "get", config.get("resource"),
        "--no-headers=true",
        "--show-labels=true",
    ], "requestTimeoutSeconds")
    command_summary = kubernetes_retention_command_summary(result)
    if result.get("exitCode") != 0:
        return {
            "ok": False,
            "error": "kubernetes-retention-list-failed",
            "items": [],
            "scan": {"scannedCount": 0, "limit": scan_limit, "truncated": True, "commands": [command_summary]},
        }
    items = []
    invalid_rows = []
    sparse_non_terminal_rows = 0
    terminal_statuses = set(config.get("terminalStatuses") or [])
    for line_number, line in enumerate((result.get("stdout") or "").splitlines(), start=1):
        if not line.strip():
            continue
        parts = line.split()
        terminal = len(parts) >= 2 and parts[1] in terminal_statuses
        valid_column_count = len(parts) == 6 if terminal else 4 <= len(parts) <= 6
        if not valid_column_count:
            invalid_rows.append({
                "line": line_number,
                "columnCount": len(parts),
                "rowPreview": line[:600],
            })
            continue
        if not terminal and len(parts) < 6:
            sparse_non_terminal_rows += 1
        labels = {}
        labels_field = parts[-1]
        if labels_field not in ["<none>", ""]:
            for label in labels_field.split(","):
                key, separator, value = label.partition("=")
                if separator and key and value:
                    labels[key] = value
            if not labels:
                invalid_rows.append({
                    "line": line_number,
                    "columnCount": len(parts),
                    "rowPreview": line[:600],
                    "reason": "labels-column-invalid",
                })
                continue
        age = parts[3] if len(parts) >= 5 else None
        completion_age = parts[4] if len(parts) == 6 else None
        items.append({
            "name": parts[0],
            "terminalStatus": parts[1],
            "terminalReason": parts[2],
            "age": age,
            "completionAge": completion_age,
            "labels": labels,
        })
    truncated = len(items) > scan_limit
    if truncated:
        items = items[:scan_limit]
    if invalid_rows:
        return {
            "ok": False,
            "error": "kubernetes-retention-table-shape-invalid",
            "items": [],
            "scan": {
                "scannedCount": len(items),
                "limit": scan_limit,
                "truncated": True,
                "invalidRows": invalid_rows[:10],
                "sparseNonTerminalRowCount": sparse_non_terminal_rows,
                "commands": [command_summary],
            },
        }
    return {
        "ok": not truncated,
        "error": "kubernetes-retention-scan-limit-reached" if truncated else None,
        "items": items,
        "scan": {
            "scannedCount": len(items),
            "totalHint": len(items),
            "limit": scan_limit,
            "mode": config.get("listMode"),
            "truncated": truncated,
            "sparseNonTerminalRowCount": sparse_non_terminal_rows,
            "commands": [command_summary],
        },
    }


def kubernetes_retention_plan(config, mode, limit_override=None, full=False):
    config = kubernetes_retention_config(config)
    if not config.get("enabled"):
        return {
            "ok": False,
            "error": "kubernetes-object-retention-disabled",
            "configId": config.get("configId"),
            "mutation": False,
        }
    listed = kubernetes_retention_list(config)
    if not listed.get("ok"):
        return {
            "ok": False,
            "error": listed.get("error"),
            "message": listed.get("message"),
            "configId": config.get("configId"),
            "namespace": config.get("namespace"),
            "mutation": False,
            "scan": listed.get("scan"),
        }
    now_timestamp = time.time()
    rows = [kubernetes_retention_row(item, config, now_timestamp) for item in listed.get("items") or []]
    rows = [item for item in rows if isinstance(item.get("name"), str) and item.get("name")]
    groups = {}
    for row in rows:
        group = row.get("group")
        if group is not None:
            groups.setdefault(group.get("key"), []).append(row)
    protected_latest = set()
    for group_rows in groups.values():
        terminal_rows = [item for item in group_rows if item.get("terminal")]
        terminal_rows.sort(key=lambda item: item.get("createdTimestamp") or 0, reverse=True)
        for item in terminal_rows[:int(config.get("keepLatestPerGroup"))]:
            protected_latest.add(item.get("name"))
    eligible = []
    protected = {
        "active": 0,
        "young": 0,
        "latestPerGroup": 0,
        "unknownGroup": 0,
        "invalidTimestamp": 0,
    }
    for row in rows:
        if not row.get("terminal"):
            protected["active"] += 1
        elif row.get("group") is None:
            protected["unknownGroup"] += 1
        elif row.get("createdTimestamp") is None or row.get("ageHours") is None:
            protected["invalidTimestamp"] += 1
        elif row.get("ageHours") < int(config.get("minAgeHours")):
            protected["young"] += 1
        elif row.get("name") in protected_latest:
            protected["latestPerGroup"] += 1
        else:
            eligible.append(row)
    eligible.sort(key=lambda item: item.get("createdTimestamp") or 0)
    configured_limit = int(config.get("automaticMaxDeletePerRun" if mode == "automatic" else "manualMaxDeletePerRun"))
    effective_limit = configured_limit
    if limit_override is not None:
        effective_limit = min(configured_limit, max(1, int(limit_override)))
    selected = eligible[:effective_limit]
    selected_names = [item.get("name") for item in selected]
    fingerprint = hashlib.sha256("\n".join(selected_names).encode("utf-8")).hexdigest()
    group_summary = []
    for group_key, group_rows in groups.items():
        group_selected = [item for item in selected if (item.get("group") or {}).get("key") == group_key]
        group_summary.append({
            "groupKey": group_key,
            "totalCount": len(group_rows),
            "terminalCount": len([item for item in group_rows if item.get("terminal")]),
            "selectedCount": len(group_selected),
        })
    group_summary.sort(key=lambda item: item.get("totalCount"), reverse=True)
    preview_limit = int(config.get("previewLimit"))
    preview = selected if full else selected[:preview_limit]
    candidates = [
        {
            "name": item.get("name"),
            "createdAt": item.get("createdAt"),
            "age": item.get("age"),
            "ageHours": item.get("ageHours"),
            "terminalStatus": item.get("terminalStatus"),
            "terminalReason": item.get("terminalReason"),
            "group": item.get("group"),
        }
        for item in preview
    ]
    return {
        "ok": True,
        "configId": config.get("configId"),
        "mode": mode,
        "namespace": config.get("namespace"),
        "resource": config.get("resource"),
        "dryRun": True,
        "mutation": False,
        "criteria": {
            "terminalConditionType": config.get("terminalConditionType"),
            "terminalStatuses": config.get("terminalStatuses"),
            "minAgeHours": config.get("minAgeHours"),
            "keepLatestPerGroup": config.get("keepLatestPerGroup"),
            "effectiveDeleteLimit": effective_limit,
            "propagationPolicy": config.get("propagationPolicy"),
        },
        "scan": listed.get("scan"),
        "summary": {
            "objectCount": len(rows),
            "terminalCount": len([item for item in rows if item.get("terminal")]),
            "eligibleCount": len(eligible),
            "selectedCount": len(selected),
            "omittedEligibleCount": max(0, len(eligible) - len(selected)),
            "protected": protected,
            "groupCount": len(groups),
        },
        "groups": group_summary if full else group_summary[:preview_limit],
        "candidates": candidates,
        "returnedCandidateCount": len(candidates),
        "omittedCandidateCount": max(0, len(selected) - len(candidates)),
        "selectedNames": selected_names,
        "planFingerprint": fingerprint,
    }


def kubernetes_retention_plan_set(config, mode, limit_override=None, full=False):
    policies = kubernetes_retention_configs(config)
    aggregate_preview_limit = int(config.get("aggregatePreviewLimit"))
    plans = [
        kubernetes_retention_plan(policy, mode, limit_override=limit_override, full=full)
        for policy in policies
    ]
    protected = {
        "active": 0,
        "young": 0,
        "latestPerGroup": 0,
        "unknownGroup": 0,
        "invalidTimestamp": 0,
    }
    for plan in plans:
        policy_protected = (plan.get("summary") or {}).get("protected") or {}
        for key in protected:
            protected[key] += int(policy_protected.get(key) or 0)
    selected_names = []
    for plan in plans:
        selected_names.extend(["%s/%s" % (plan.get("namespace"), name) for name in plan.get("selectedNames") or []])
    public_plans = []
    for plan in plans:
        if full:
            public_plan = dict(plan)
            public_plan.pop("selectedNames", None)
        else:
            scan = plan.get("scan") or {}
            group_preview = (plan.get("groups") or [])[:aggregate_preview_limit]
            candidate_preview = (plan.get("candidates") or [])[:aggregate_preview_limit]
            public_plan = {
                "ok": plan.get("ok") is True,
                "configId": plan.get("configId"),
                "namespace": plan.get("namespace"),
                "resource": plan.get("resource"),
                "criteria": plan.get("criteria"),
                "scan": {
                    key: scan.get(key)
                    for key in ["scannedCount", "totalHint", "limit", "mode", "truncated", "sparseNonTerminalRowCount"]
                    if scan.get(key) is not None
                },
                "summary": plan.get("summary"),
                "groups": [
                    {
                        key: group.get(key)
                        for key in ["groupKey", "totalCount", "terminalCount", "selectedCount"]
                    }
                    for group in group_preview
                ],
                "candidates": [
                    {
                        "name": candidate.get("name"),
                        "age": candidate.get("age"),
                        "ageHours": candidate.get("ageHours"),
                        "terminalStatus": candidate.get("terminalStatus"),
                        "groupKey": (candidate.get("group") or {}).get("key"),
                    }
                    for candidate in candidate_preview
                ],
                "returnedCandidateCount": len(candidate_preview),
                "omittedCandidateCount": max(
                    0,
                    int((plan.get("summary") or {}).get("selectedCount") or 0) - len(candidate_preview),
                ),
                "planFingerprint": plan.get("planFingerprint"),
            }
        public_plans.append(public_plan)
    return {
        "ok": all(plan.get("ok") is True for plan in plans),
        "mode": mode,
        "dryRun": True,
        "mutation": False,
        "policyCount": len(plans),
        "summary": {
            "objectCount": sum(int((plan.get("summary") or {}).get("objectCount") or 0) for plan in plans),
            "terminalCount": sum(int((plan.get("summary") or {}).get("terminalCount") or 0) for plan in plans),
            "eligibleCount": sum(int((plan.get("summary") or {}).get("eligibleCount") or 0) for plan in plans),
            "selectedCount": sum(int((plan.get("summary") or {}).get("selectedCount") or 0) for plan in plans),
            "omittedEligibleCount": sum(int((plan.get("summary") or {}).get("omittedEligibleCount") or 0) for plan in plans),
            "groupCount": sum(int((plan.get("summary") or {}).get("groupCount") or 0) for plan in plans),
            "protected": protected,
        },
        "policies": public_plans,
        "planFingerprint": hashlib.sha256("\n".join(selected_names).encode("utf-8")).hexdigest(),
    }


def kubernetes_retention_count(config):
    base_path = config.get("countPath").replace(
        "{namespace}", urllib.parse.quote(config.get("namespace"), safe="")
    )
    separator = "&" if "?" in base_path else "?"
    result = kubernetes_retention_command(config, [
        "kubectl", "--kubeconfig", config.get("kubeconfigPath"), "get", "--raw", "%s%slimit=1" % (base_path, separator),
    ], "requestTimeoutSeconds")
    command_summary = kubernetes_retention_command_summary(result)
    command_summary.pop("stdoutTail", None)
    if result.get("exitCode") != 0:
        return {"ok": False, "command": command_summary}
    try:
        payload = json.loads(result.get("stdout") or "{}")
        metadata = payload.get("metadata") or {}
        return {
            "ok": True,
            "count": len(payload.get("items") or []) + int(metadata.get("remainingItemCount") or 0),
            "command": command_summary,
        }
    except (TypeError, ValueError, json.JSONDecodeError) as exc:
        return {"ok": False, "error": str(exc), "command": command_summary}


def kubernetes_retention_execute(config, mode, limit_override=None, progress=None, full=False):
    config = kubernetes_retention_config(config)
    before = kubernetes_retention_count(config)
    plan = kubernetes_retention_plan(config, mode, limit_override=limit_override, full=True)
    if not plan.get("ok"):
        return {"ok": False, "plan": plan, "before": before, "mutation": False, "deletedCount": 0}
    selected_names = list(plan.get("selectedNames") or [])
    if not selected_names:
        return {"ok": True, "plan": plan, "before": before, "after": before, "mutation": False, "deletedCount": 0, "batches": []}
    batch_size = int(config.get("deleteBatchSize"))
    batches = []
    deleted_count = 0
    for offset in range(0, len(selected_names), batch_size):
        names = selected_names[offset:offset + batch_size]
        result = kubernetes_retention_command(config, [
            "kubectl",
            "--kubeconfig", config.get("kubeconfigPath"),
            "--namespace", config.get("namespace"),
            "delete", config.get("resource"),
            "--ignore-not-found=true",
            "--wait=false",
            "--cascade=%s" % config.get("propagationPolicy").lower(),
        ] + names, "deleteTimeoutSeconds")
        ok = result.get("exitCode") == 0
        if ok:
            deleted_count += len(names)
        batches.append({
            "index": len(batches),
            "requestedCount": len(names),
            "ok": ok,
            "command": kubernetes_retention_command_summary(result),
        })
        if progress is not None:
            progress(deleted_count, len(selected_names))
        if not ok:
            break
    settle_seconds = int(config.get("postDeleteSettleSeconds"))
    if settle_seconds > 0:
        time.sleep(settle_seconds)
    after = kubernetes_retention_count(config)
    ok = all(item.get("ok") for item in batches)
    compact_plan = dict(plan)
    if not full:
        compact_plan.pop("selectedNames", None)
        compact_plan["candidates"] = compact_plan.get("candidates", [])[:int(config.get("previewLimit"))]
    return {
        "ok": ok,
        "configId": config.get("configId"),
        "mode": mode,
        "namespace": config.get("namespace"),
        "resource": config.get("resource"),
        "mutation": deleted_count > 0,
        "requestedDeleteCount": len(selected_names),
        "deletedCount": deleted_count,
        "before": before,
        "after": after,
        "objectCountDelta": (
            int(before.get("count")) - int(after.get("count"))
            if before.get("ok") and after.get("ok") else None
        ),
        "plan": compact_plan,
        "batches": batches,
    }


def kubernetes_retention_execute_set(config, mode, limit_override=None, progress=None, full=False):
    policies = kubernetes_retention_configs(config)
    preplans = [
        kubernetes_retention_plan(policy, mode, limit_override=limit_override, full=True)
        for policy in policies
    ]
    if not all(plan.get("ok") is True for plan in preplans):
        return {
            "ok": False,
            "mutation": False,
            "requestedDeleteCount": 0,
            "deletedCount": 0,
            "policies": preplans,
        }
    candidate_count = sum(int((plan.get("summary") or {}).get("selectedCount") or 0) for plan in preplans)
    completed_count = 0
    results = []
    for policy in policies:
        base_completed = completed_count

        def policy_progress(local_completed, _local_candidates, base=base_completed):
            if progress is not None:
                progress(base + local_completed, candidate_count)

        result = kubernetes_retention_execute(
            policy,
            mode,
            limit_override=limit_override,
            progress=policy_progress,
            full=full,
        )
        results.append(result)
        completed_count += int(result.get("deletedCount") or 0)
        if result.get("ok") is not True:
            break
    before_values = [result.get("before") or {} for result in results]
    after_values = [result.get("after") or {} for result in results]
    before_ok = len(before_values) == len(policies) and all(item.get("ok") is True for item in before_values)
    after_ok = len(after_values) == len(policies) and all(item.get("ok") is True for item in after_values)
    before = {"ok": before_ok, "count": sum(int(item.get("count") or 0) for item in before_values)}
    after = {"ok": after_ok, "count": sum(int(item.get("count") or 0) for item in after_values)}
    requested_count = sum(int(result.get("requestedDeleteCount") or 0) for result in results)
    deleted_count = sum(int(result.get("deletedCount") or 0) for result in results)
    return {
        "ok": len(results) == len(policies) and all(result.get("ok") is True for result in results),
        "mode": mode,
        "policyCount": len(policies),
        "mutation": deleted_count > 0,
        "requestedDeleteCount": requested_count,
        "deletedCount": deleted_count,
        "before": before,
        "after": after,
        "objectCountDelta": before.get("count") - after.get("count") if before_ok and after_ok else None,
        "policies": results,
    }


def run_kubernetes_object_retention_policy(config):
    retention = config.get("kubernetesObjectRetention") if isinstance(config.get("kubernetesObjectRetention"), dict) else {}
    if not retention.get("enabled"):
        return {"id": "kubernetes-object-retention", "ok": True, "skipped": True}
    result = kubernetes_retention_execute_set(retention, "automatic", full=False)
    return {
        "id": "kubernetes-object-retention",
        "ok": result.get("ok") is True,
        "skipped": result.get("mutation") is not True,
        "policyCount": result.get("policyCount"),
        "requestedDeleteCount": result.get("requestedDeleteCount"),
        "deletedCount": result.get("deletedCount"),
        "objectCountDelta": result.get("objectCountDelta"),
        "before": result.get("before"),
        "after": result.get("after"),
        "policies": result.get("policies"),
        "error": "kubernetes-retention-policy-set-failed" if not result.get("ok") else None,
    }
