#!/usr/bin/env python3
"""Offline HA/resource checks. Requires PyYAML; never contacts Kubernetes."""
import sys
import yaml

objects = [x for x in yaml.safe_load_all(open(sys.argv[1])) if isinstance(x, dict)]
pdbs = [x for x in objects if x.get('kind') == 'PodDisruptionBudget']
errors = []
if not any(x.get("kind") in ("Deployment", "StatefulSet", "DaemonSet") for x in objects):
    raise SystemExit("No workloads found; check helm render succeeded")

def matches(selector, labels):
    if not selector:
        return False
    if not all(labels.get(k) == v for k, v in selector.get('matchLabels', {}).items()):
        return False
    for term in selector.get('matchExpressions', []):
        key, op, vals = term['key'], term['operator'], term.get('values', [])
        ok = {'In': key in labels and labels[key] in vals,
              'NotIn': key not in labels or labels[key] not in vals,
              'Exists': key in labels, 'DoesNotExist': key not in labels}.get(op, False)
        if not ok:
            return False
    return True

for obj in objects:
    if obj.get('kind') not in ('Deployment', 'StatefulSet', 'DaemonSet'):
        continue
    name = obj['metadata']['name']
    spec = obj['spec']
    pod = spec['template']['spec']
    labels = spec['template']['metadata'].get('labels', {})
    for container in pod.get('containers', []):
        r = container.get('resources') or {}
        if not all(r.get('requests', {}).get(k) for k in ('cpu', 'memory')) or not r.get('limits', {}).get('memory'):
            errors.append(f'{name}/{container["name"]}: missing CPU/memory request or memory limit')
    if obj['kind'] == 'DaemonSet':
        continue
    if spec.get('replicas', 1) < 2:
        errors.append(f'{name}: replicas < 2')
    terms = pod.get('affinity', {}).get('podAntiAffinity', {}).get('requiredDuringSchedulingIgnoredDuringExecution', [])
    if not any(t.get('topologyKey') == 'kubernetes.io/hostname' and matches(t.get('labelSelector'), labels) for t in terms):
        errors.append(f'{name}: no required node anti-affinity matching own labels')
    if not any(matches(p['spec'].get('selector'), labels) and (p['spec'].get('minAvailable', 0) or p['spec'].get('maxUnavailable') is not None) for p in pdbs):
        errors.append(f'{name}: no matching PDB')
if errors:
    raise SystemExit('\n'.join(errors))
print('PASS: controller replicas, resources, matching node anti-affinity and PDB; DaemonSet resources')
