"""Kubernetes manifest linter — 20 rules for K8s best practices."""

from __future__ import annotations

from ..models import Finding, LintRule, Severity

RULES: dict[str, LintRule] = {
    "K8S001": LintRule("K8S001", "Security", Severity.CRITICAL, "Container running as root", "Container has no securityContext or runs as root", "Add securityContext.runAsNonRoot: true"),
    "K8S002": LintRule("K8S002", "Security", Severity.HIGH, "Missing resource limits", "Container missing CPU/memory limits", "Add resources.limits.cpu and resources.limits.memory"),
    "K8S003": LintRule("K8S003", "Security", Severity.HIGH, "Missing resource requests", "Container missing CPU/memory requests", "Add resources.requests.cpu and resources.requests.memory"),
    "K8S004": LintRule("K8S004", "Security", Severity.CRITICAL, "Privileged container", "Container running in privileged mode", "Set securityContext.privileged: false"),
    "K8S005": LintRule("K8S005", "Security", Severity.HIGH, "Using :latest tag", "Image tag is :latest or unspecified", "Pin image to a specific version tag"),
    "K8S006": LintRule("K8S006", "Reliability", Severity.HIGH, "Missing liveness probe", "No livenessProbe configured", "Add livenessProbe with httpGet, tcpSocket, or exec"),
    "K8S007": LintRule("K8S007", "Reliability", Severity.MEDIUM, "Missing readiness probe", "No readinessProbe configured", "Add readinessProbe for proper load balancing"),
    "K8S008": LintRule("K8S008", "Security", Severity.MEDIUM, "Read-only filesystem not set", "Root filesystem is writable", "Set securityContext.readOnlyRootFilesystem: true"),
    "K8S009": LintRule("K8S009", "Best Practice", Severity.LOW, "Missing labels", "Pod/Deployment missing standard labels", "Add app.kubernetes.io/name, app.kubernetes.io/version labels"),
    "K8S010": LintRule("K8S010", "Best Practice", Severity.INFO, "Missing namespace", "Resource has no namespace specified", "Add metadata.namespace or deploy with -n flag"),
    "K8S011": LintRule("K8S011", "Security", Severity.HIGH, "hostNetwork enabled", "Pod uses host network namespace", "Remove hostNetwork: true unless absolutely needed"),
    "K8S012": LintRule("K8S012", "Security", Severity.HIGH, "hostPID enabled", "Pod uses host PID namespace", "Remove hostPID: true"),
    "K8S013": LintRule("K8S013", "Reliability", Severity.MEDIUM, "Single replica deployment", "Deployment has replicas: 1", "Use at least 2 replicas for high availability"),
    "K8S014": LintRule("K8S014", "Security", Severity.MEDIUM, "Allow privilege escalation", "allowPrivilegeEscalation not set to false", "Set securityContext.allowPrivilegeEscalation: false"),
    "K8S015": LintRule("K8S015", "Best Practice", Severity.LOW, "Missing pod disruption budget", "No PDB for the deployment", "Create a PodDisruptionBudget resource"),
    "K8S016": LintRule("K8S016", "Security", Severity.HIGH, "Secrets in env vars", "Secrets referenced directly in env instead of secretKeyRef", "Use secretKeyRef or mount secrets as volumes"),
    "K8S017": LintRule("K8S017", "Reliability", Severity.MEDIUM, "No update strategy", "Deployment missing strategy specification", "Add strategy.type: RollingUpdate with maxSurge/maxUnavailable"),
    "K8S018": LintRule("K8S018", "Security", Severity.MEDIUM, "Capabilities not dropped", "Container not dropping Linux capabilities", "Add securityContext.capabilities.drop: [ALL]"),
    "K8S019": LintRule("K8S019", "Best Practice", Severity.LOW, "No anti-affinity rules", "Deployment has no pod anti-affinity for HA", "Add podAntiAffinity to spread pods across nodes"),
    "K8S020": LintRule("K8S020", "Reliability", Severity.MEDIUM, "Missing restart policy", "Pod has no restartPolicy specified (defaults to Always)", "Explicitly set restartPolicy"),
}

_WORKLOAD_KINDS = {"Deployment", "StatefulSet", "DaemonSet", "Job", "CronJob", "ReplicaSet"}


def lint_kubernetes(docs: list[dict], file_path: str = "") -> list[Finding]:
    """Lint Kubernetes manifests."""
    findings: list[Finding] = []

    for doc in docs:
        if not isinstance(doc, dict):
            continue
        kind = doc.get("kind", "")
        if not kind:
            continue

        metadata = doc.get("metadata", {}) or {}
        spec = doc.get("spec", {}) or {}

        # For workloads, get the pod template spec
        if kind in _WORKLOAD_KINDS:
            template = spec.get("template", {}) or {}
            pod_spec = (template.get("spec", {}) or {})
            containers = pod_spec.get("containers", []) or []
        elif kind == "Pod":
            pod_spec = spec
            containers = pod_spec.get("containers", []) or []
        else:
            containers = []
            pod_spec = {}

        # K8S010: Missing namespace
        if not metadata.get("namespace"):
            findings.append(Finding(rule=RULES["K8S010"], line=0, message=f"{kind}/{metadata.get('name', '?')}: No namespace specified"))

        # K8S009: Missing labels
        labels = metadata.get("labels", {}) or {}
        if kind in _WORKLOAD_KINDS or kind == "Pod":
            if not labels.get("app") and not labels.get("app.kubernetes.io/name"):
                findings.append(Finding(rule=RULES["K8S009"], line=0, message=f"{kind}/{metadata.get('name', '?')}: Missing standard labels (app or app.kubernetes.io/name)"))

        # Workload-specific checks
        if kind in _WORKLOAD_KINDS:
            # K8S013: Single replica
            replicas = spec.get("replicas")
            if kind in ("Deployment", "ReplicaSet", "StatefulSet") and replicas is not None and replicas < 2:
                findings.append(Finding(rule=RULES["K8S013"], line=0, message=f"{kind}/{metadata.get('name', '?')}: replicas={replicas} — use >=2 for HA"))

            # K8S017: No update strategy
            if kind == "Deployment" and not spec.get("strategy"):
                findings.append(Finding(rule=RULES["K8S017"], line=0, message=f"Deployment/{metadata.get('name', '?')}: No update strategy defined"))

        # Pod-level checks
        if pod_spec:
            # K8S011: hostNetwork
            if pod_spec.get("hostNetwork"):
                findings.append(Finding(rule=RULES["K8S011"], line=0, message=f"{kind}/{metadata.get('name', '?')}: hostNetwork is enabled"))

            # K8S012: hostPID
            if pod_spec.get("hostPID"):
                findings.append(Finding(rule=RULES["K8S012"], line=0, message=f"{kind}/{metadata.get('name', '?')}: hostPID is enabled"))

        # Container-level checks
        for c in containers:
            cname = c.get("name", "unnamed")
            prefix = f"{kind}/{metadata.get('name', '?')}/{cname}"

            # K8S001: Running as root
            sec_ctx = c.get("securityContext", {}) or {}
            pod_sec = pod_spec.get("securityContext", {}) or {}
            if not sec_ctx.get("runAsNonRoot") and not pod_sec.get("runAsNonRoot"):
                run_as = sec_ctx.get("runAsUser", pod_sec.get("runAsUser"))
                if run_as is None or run_as == 0:
                    findings.append(Finding(rule=RULES["K8S001"], line=0, message=f"{prefix}: No runAsNonRoot and no non-root runAsUser"))

            # K8S004: Privileged
            if sec_ctx.get("privileged"):
                findings.append(Finding(rule=RULES["K8S004"], line=0, message=f"{prefix}: Running in privileged mode"))

            # K8S014: allowPrivilegeEscalation
            if sec_ctx.get("allowPrivilegeEscalation") is not False and not sec_ctx.get("readOnlyRootFilesystem"):
                if "allowPrivilegeEscalation" not in sec_ctx:
                    findings.append(Finding(rule=RULES["K8S014"], line=0, message=f"{prefix}: allowPrivilegeEscalation not explicitly set to false"))

            # K8S018: Capabilities not dropped
            caps = sec_ctx.get("capabilities", {}) or {}
            drop = caps.get("drop", []) or []
            if "ALL" not in [str(d).upper() for d in drop]:
                findings.append(Finding(rule=RULES["K8S018"], line=0, message=f"{prefix}: Not dropping ALL capabilities"))

            # K8S008: Read-only root filesystem
            if not sec_ctx.get("readOnlyRootFilesystem"):
                findings.append(Finding(rule=RULES["K8S008"], line=0, message=f"{prefix}: Root filesystem is writable"))

            # K8S002/K8S003: Resources
            resources = c.get("resources", {}) or {}
            limits = resources.get("limits", {}) or {}
            requests = resources.get("requests", {}) or {}
            if not limits.get("cpu") or not limits.get("memory"):
                findings.append(Finding(rule=RULES["K8S002"], line=0, message=f"{prefix}: Missing resource limits"))
            if not requests.get("cpu") or not requests.get("memory"):
                findings.append(Finding(rule=RULES["K8S003"], line=0, message=f"{prefix}: Missing resource requests"))

            # K8S005: :latest tag
            image = c.get("image", "")
            if image and (":" not in image or image.endswith(":latest")):
                findings.append(Finding(rule=RULES["K8S005"], line=0, message=f"{prefix}: Image '{image}' uses :latest or no tag"))

            # K8S006/K8S007: Probes
            if kind in ("Deployment", "StatefulSet", "DaemonSet"):
                if not c.get("livenessProbe"):
                    findings.append(Finding(rule=RULES["K8S006"], line=0, message=f"{prefix}: No livenessProbe configured"))
                if not c.get("readinessProbe"):
                    findings.append(Finding(rule=RULES["K8S007"], line=0, message=f"{prefix}: No readinessProbe configured"))

            # K8S016: Secrets in env
            env_vars = c.get("env", []) or []
            for ev in env_vars:
                name = (ev.get("name", "") or "").upper()
                value = ev.get("value", "")
                if value and any(kw in name for kw in ("SECRET", "PASSWORD", "TOKEN", "API_KEY", "PRIVATE_KEY")):
                    findings.append(Finding(rule=RULES["K8S016"], line=0, message=f"{prefix}: Potential secret '{ev.get('name')}' in plain env var"))

    return findings
