from __future__ import annotations
import json, subprocess
from pathlib import Path
import pytest
from adw_modules import kubernetes_job

IMAGE=kubernetes_job.APPROVED_IMAGE
EXEC="refs/heads/factory-exec/"+"1"*24
RESULT="refs/heads/factory-result/"+"1"*24

def test_job_is_portable_restricted_and_secret_backed() -> None:
    job=kubernetes_job.manifest(job_name="factory-abc",namespace="overdeck-factory",image=IMAGE,secret="overdeck-factory-credentials",service_account="overdeck-factory-runner",repo_url="https://github.com/alexcodeplace/overdeck.git",execution_ref=EXEC,result_ref=RESULT,invocation=["build","request"],timeout=600)
    encoded=json.dumps(job)
    assert "hostPath" not in encoded and "nodeSelector" not in encoded and "ConfigMap" not in encoded and "ssh" not in encoded.lower()
    pod=job["spec"]["template"]["spec"]; container=pod["containers"][0]
    assert pod["automountServiceAccountToken"] is False
    assert pod["imagePullSecrets"] == [{"name":"overdeck-ghcr-pull"}]
    assert container["imagePullPolicy"] == "Always"
    assert not any(item["name"] == "PATH" for item in container["env"])
    assert pod["volumes"][-1]["secret"]["secretName"] == "overdeck-factory-credentials"
    assert container["securityContext"] == {"allowPrivilegeEscalation":False,"readOnlyRootFilesystem":True,"capabilities":{"drop":["ALL"]}}
    env={item["name"]:item["value"] for item in container["env"]}
    assert env["FACTORY_EXECUTION_REF"] == EXEC and env["FACTORY_RESULT_REF"] == RESULT

def test_job_rejects_mutable_unapproved_image_or_repository() -> None:
    for image,repo_url in (
        ("ghcr.io/alexcodeplace/overdeck-agent-sandbox:latest","https://github.com/alexcodeplace/overdeck.git"),
        ("ghcr.io/attacker/overdeck-agent-sandbox@sha256:" + "a" * 64,"https://github.com/alexcodeplace/overdeck.git"),
        (IMAGE,"https://github.com/alexcodeplace/../attacker/repo.git"),
    ):
        with pytest.raises(ValueError,match="approved (immutable GHCR digest|repository URL)"):
            kubernetes_job.manifest(job_name="factory-abc",namespace="overdeck-factory",image=image,secret="credentials",service_account="runner",repo_url=repo_url,execution_ref=EXEC,result_ref=RESULT,invocation=["build"],timeout=600)

def init_repo(path: Path) -> None:
    subprocess.run(["git","init","-q",str(path)],check=True)
    subprocess.run(["git","-C",str(path),"config","user.email","test@example.invalid"],check=True)
    subprocess.run(["git","-C",str(path),"config","user.name","Test"],check=True)
    (path/".gitignore").write_text("ignored\n"); (path/"tracked").write_text("before\n")
    subprocess.run(["git","-C",str(path),"add",".gitignore","tracked"],check=True)
    subprocess.run(["git","-C",str(path),"commit","-qm","base"],check=True)

def test_workspace_snapshot_includes_source_but_not_ignored_data(tmp_path: Path) -> None:
    init_repo(tmp_path); (tmp_path/"tracked").write_text("after\n"); (tmp_path/"new-source").write_text("new\n"); (tmp_path/"ignored").write_text("private\n")
    _,_,commit=kubernetes_job.workspace_snapshot(tmp_path)
    names=subprocess.check_output(["git","-C",str(tmp_path),"ls-tree","-r","--name-only",commit],text=True).splitlines()
    assert "tracked" in names and "new-source" in names and "ignored" not in names
    assert (tmp_path/"ignored").read_text() == "private\n"

def test_delete_remote_ref_is_idempotent(tmp_path: Path) -> None:
    repo=tmp_path/"repo"; remote=tmp_path/"remote.git"; repo.mkdir(); init_repo(repo)
    subprocess.run(["git","init","--bare","-q",str(remote)],check=True)
    subprocess.run(["git","-C",str(repo),"remote","add","origin",str(remote)],check=True)
    ref="refs/heads/factory-exec/"+"2"*24
    subprocess.run(["git","-C",str(repo),"push","-q","origin",f"HEAD:{ref}"],check=True)
    assert kubernetes_job.delete_remote_ref(repo,ref) is None
    assert kubernetes_job.delete_remote_ref(repo,ref) is None

def test_failed_job_retains_result_ref_for_recovery(tmp_path: Path, monkeypatch) -> None:
    repo=tmp_path/"repo"; remote=tmp_path/"remote.git"; repo.mkdir(); init_repo(repo)
    subprocess.run(["git","init","--bare","-q",str(remote)],check=True)
    subprocess.run(["git","-C",str(repo),"remote","add","origin",str(remote)],check=True)
    nonce="a"*24; result_ref=f"refs/heads/factory-result/{nonce}"; execution_ref=f"refs/heads/factory-exec/{nonce}"
    subprocess.run(["git","-C",str(repo),"push","-q","origin",f"HEAD:{result_ref}"],check=True)
    actual_run=kubernetes_job.run

    def failed_job(argv, **kwargs):
        if argv[0] != "kubectl": return actual_run(argv, **kwargs)
        if "get" in argv: return subprocess.CompletedProcess(argv,0,'{"status":{"failed":1}}',"")
        return subprocess.CompletedProcess(argv,0,"","")

    monkeypatch.setattr(kubernetes_job,"run",failed_job)
    monkeypatch.setattr(kubernetes_job,"origin",lambda _: "https://github.com/alexcodeplace/overdeck.git")
    monkeypatch.setattr(kubernetes_job.secrets,"token_hex",lambda _: nonce)
    monkeypatch.setenv("FACTORY_K8S_IMAGE",IMAGE)
    with pytest.raises(RuntimeError,match="failed") as error:
        kubernetes_job.submit(repo,["build","sentinel"])
    assert result_ref in str(error.value.__notes__)
    assert subprocess.run(["git","-C",str(repo),"ls-remote","--exit-code","origin",result_ref],capture_output=True).returncode == 0
    assert subprocess.run(["git","-C",str(repo),"ls-remote","--exit-code","origin",execution_ref],capture_output=True).returncode != 0
    recovery=repo/".git"/"factory-kubernetes"/f"factory-{nonce[:16]}"/"recovery.json"
    assert recovery.exists() and json.loads(recovery.read_text())["result_ref"] == result_ref


def test_worker_runtime_and_workflow_parse() -> None:
    root=Path(__file__).parents[1]
    subprocess.run(["python3","-m","py_compile",str(root/"kubernetes"/"factory-kubernetes-worker")],check=True)
    runtime=(root/"kubernetes"/"runtime.yaml").read_text()
    assert "hostPath" not in runtime and "configmaps" not in runtime and "pod-security.kubernetes.io/audit: restricted" in runtime
    assert "kind: ValidatingAdmissionPolicy" in runtime
    assert "kind: ValidatingAdmissionPolicyBinding" in runtime
    assert kubernetes_job.APPROVED_IMAGE in runtime
    assert "e.value == 'https://github.com/alexcodeplace/overdeck.git'" in runtime
    assert "params.data" not in runtime
    assert "kind: ConfigMap" not in runtime
    assert "startsWith(params.data.repository" not in runtime
    assert "kind: RoleBinding" not in runtime
    assert "overdeck-factory-credentials" in runtime
    workflow=Path(__file__).parents[4]/".github/workflows/factory-kubernetes-image.yml"
    assert "packages: write" in workflow.read_text() and "ghcr.io/alexcodeplace/overdeck-agent-sandbox" in workflow.read_text()
    assert "branches: [main]" in workflow.read_text() and "workflow_dispatch" not in workflow.read_text()
