"""Individual doctor checks C1/C6/C7/C8 + report shape (SPEC §5)."""

from __future__ import annotations

from mgf.secrets.doctor import Doctor
from mgf.secrets.hosts import HostStat
from mgf.secrets.registry import load
from tests._fakes import FakeHost, FakeWoodpecker, write_pipeline


def _reg(tmp_path, text):
    p = tmp_path / "SECRETS.toml"
    p.write_text(text, encoding="utf-8")
    return load([p])


ORG = """\
[s1]
family = "mgf"
kind = "api-token"
purpose = "p"
consumers = ["c"]
recovery = "r"
state = "active"
policy = { allowed_repos = "*", blast_radius = "b" }
rotation = { class = "token", staged = true }
verify = { probe = "auth" }
surfaces = [ { type = "woodpecker-org-secret", events = ["push", "tag"] } ]
"""


def test_c1_missing_secret(tmp_path) -> None:
    reg = _reg(tmp_path, ORG)
    wp = FakeWoodpecker(org_secrets={}, repos=[])
    findings = Doctor(reg, fed_root=tmp_path, woodpecker=wp).c1_declared_actual()
    assert findings and findings[0].check == "C1" and "MISSING" in findings[0].finding


def test_c1_scope_mismatch(tmp_path) -> None:
    reg = _reg(tmp_path, ORG)
    wp = FakeWoodpecker(org_secrets={"s1": {"push"}}, repos=[])  # missing 'tag'
    findings = Doctor(reg, fed_root=tmp_path, woodpecker=wp).c1_declared_actual()
    assert findings and "!= declared" in findings[0].finding


def test_c1_clean_when_exact(tmp_path) -> None:
    reg = _reg(tmp_path, ORG)
    wp = FakeWoodpecker(org_secrets={"s1": {"push", "tag"}}, repos=[])
    assert Doctor(reg, fed_root=tmp_path, woodpecker=wp).c1_declared_actual() == []


C8 = """\
[scoped_secret]
family = "mgf"
kind = "api-token"
purpose = "p"
consumers = ["c"]
recovery = "r"
state = "active"
policy = { allowed_repos = ["mgf-cost-watch"], blast_radius = "b" }
rotation = { class = "token", staged = true }
verify = { probe = "auth" }
surfaces = [ { type = "woodpecker-org-secret", events = ["push"] } ]
"""


def test_c8_policy_out_of_allowed_repos(tmp_path) -> None:
    reg = _reg(tmp_path, C8)
    write_pipeline(
        tmp_path,
        "mgf-website",
        """\
when:
  - event: push
steps:
  - name: x
    environment:
      E:
        from_secret: scoped_secret
    commands: [echo x]
""",
    )
    wp = FakeWoodpecker(org_secrets={"scoped_secret": {"push"}}, repos=[(1, "mgf-website")])
    findings = Doctor(reg, fed_root=tmp_path, woodpecker=wp).run(["woodpecker"])
    c8 = [f for f in findings.findings if f.check == "C8"]
    assert c8 and c8[0].evidence["repo"] == "mgf-website"


HOSTFILE = """\
[oo_root]
family = "mgf"
kind = "service-account"
purpose = "p"
consumers = ["c"]
recovery = "r"
state = "active"
policy = { allowed_repos = "*", blast_radius = "b" }
rotation = { class = "sa", staged = true }
verify = { probe = "oo" }
surfaces = [ { type = "host-file", host = "h1", path = "/etc/mgf/x", mode = "0600", owner = "root" } ]
"""


def test_c7_missing_and_mode_owner_drift(tmp_path) -> None:
    reg = _reg(tmp_path, HOSTFILE)
    # missing
    hosts = FakeHost({("h1", "/etc/mgf/x"): HostStat("missing")})
    f = Doctor(reg, fed_root=tmp_path, hosts=hosts).c7_hosts()
    assert f and "MISSING" in f[0].finding and f[0].severity == "high"
    # mode + owner drift
    hosts = FakeHost({("h1", "/etc/mgf/x"): HostStat("present", mode="644", owner="ubuntu")})
    f = Doctor(reg, fed_root=tmp_path, hosts=hosts).c7_hosts()
    kinds = {x.finding for x in f}
    assert any("mode" in m for m in kinds) and any("owner" in m for m in kinds)


def test_c7_clean(tmp_path) -> None:
    reg = _reg(tmp_path, HOSTFILE)
    hosts = FakeHost({("h1", "/etc/mgf/x"): HostStat("present", mode="600", owner="root")})
    assert Doctor(reg, fed_root=tmp_path, hosts=hosts).c7_hosts() == []


def test_c7_unreachable_is_not_a_missing_finding(tmp_path) -> None:
    """A present-but-unverifiable host-file (e.g. /opt/steward 0750 traversal) must
    NEVER page as MISSING — it is recorded in skipped, not as a finding (F-1b)."""
    reg = _reg(tmp_path, HOSTFILE)
    hosts = FakeHost({("h1", "/etc/mgf/x"): HostStat("unreachable", detail="permission denied")})
    doc = Doctor(reg, fed_root=tmp_path, hosts=hosts)
    findings = doc.c7_hosts()
    assert findings == []  # not a finding
    assert any("unverifiable" in s for s in doc.skipped)  # but recorded


LOCAL = """\
[dpw]
family = "mgf"
kind = "password"
purpose = "p"
consumers = ["c"]
recovery = "r"
state = "active"
policy = { allowed_repos = "*", blast_radius = "b" }
rotation = { class = "password", staged = true }
verify = { probe = "login" }
surfaces = [ { type = "local-file", repo = "mgf", path = "tokens/dpw.txt", mode = "0600" } ]
"""


def test_c6_mode_drift_and_leftover_and_gitignore(tmp_path) -> None:
    reg = _reg(tmp_path, LOCAL)
    base = tmp_path / "mgf" / "my_stuff"
    (base / "tokens").mkdir(parents=True)
    f = base / "tokens" / "dpw.txt"
    f.write_text("single-line-value", encoding="utf-8")
    f.chmod(0o644)  # mode drift vs declared 0600
    (base / "old_leftover.txt").write_text("x", encoding="utf-8")  # .bak/old_ leftover
    # no .gitignore covering my_stuff -> a finding
    findings = Doctor(reg, fed_root=tmp_path).c6_local_hygiene()
    msgs = [x.finding for x in findings]
    assert any("mode 644" in m for m in msgs)
    assert any("leftover" in m for m in msgs)
    assert any("gitignore" in m for m in msgs)


CLOUDS = """\
[oscred]
family = "mgf"
kind = "openstack-appcred"
purpose = "p"
consumers = ["c"]
recovery = "r"
state = "active"
policy = { allowed_repos = "*", blast_radius = "b" }
rotation = { class = "appcred", staged = true }
verify = { probe = "openstack" }
surfaces = [ { type = "local-file", repo = "mgf", path = "infomaniak/clouds.yaml", mode = "0600" } ]
"""


def test_c6_openstack_clouds_yaml_is_not_doc_form(tmp_path) -> None:
    """A clouds.yaml is legitimately multi-line YAML — never the doc-form leak class."""
    reg = _reg(tmp_path, CLOUDS)
    f = tmp_path / "mgf" / "my_stuff" / "infomaniak" / "clouds.yaml"
    f.parent.mkdir(parents=True)
    f.write_text("clouds:\n  mycloud:\n    auth:\n      username: x\n", encoding="utf-8")
    (tmp_path / "mgf" / ".gitignore").write_text("my_stuff/\n", encoding="utf-8")
    (tmp_path / "mgf" / "my_stuff").chmod(0o700)
    findings = Doctor(reg, fed_root=tmp_path).c6_local_hygiene()
    assert not any("DOC-FORM" in x.finding for x in findings)


def test_report_json_shape_and_severity_sort(tmp_path) -> None:
    reg = _reg(tmp_path, ORG)
    wp = FakeWoodpecker(org_secrets={}, repos=[])
    report = Doctor(reg, fed_root=tmp_path, woodpecker=wp).run(["woodpecker"])
    js = report.to_json()
    assert set(js) == {"surfaces", "clean", "findings", "skipped"}
    for f in js["findings"]:
        assert set(f) == {"id", "check", "severity", "finding", "evidence"}
