from auditwalk.runs.run_store import RunStore


def test_run_store_smoke(tmp_path):
    store = RunStore(data_dir=tmp_path)
    run_id = "run_test"
    store.create_run(
        run_id,
        {
            "meta": {
                "schema_version": 1,
                "product": "auditwalk",
                "product_version": "0.1.0",
                "run_id": run_id,
                "created_utc": 1,
                "mode": "quick",
                "host": {
                    "hostname": "test-host",
                    "os_family": "linux",
                    "arch": "x86_64",
                },
                "collector_status": {
                    "system": "ok",
                    "disk": "ok",
                    "processes": "ok",
                    "network": "ok",
                    "persistence": "ok",
                    "installed": "skipped",
                    "files": "ok",
                    "firewall": "skipped",
                },
                "options": {
                    "roots": ["/tmp"],
                    "exclude": [],
                    "hash": False,
                    "max_files": 100,
                    "max_procs": 10,
                    "timeout_seconds": 60,
                },
            },
            "score": {"total": 0, "tier": "Clean"},
        },
        mode="quick",
        label="t",
    )
    ids = [r.run_id for r in store.list_runs()]
    assert run_id in ids

    store.save_score(run_id, {"total": 42, "tier": "Low", "breakdown": {}, "findings": []})
    refs = store.list_runs(last=1)
    assert refs[0].score_total == 42
    assert refs[0].tier == "Low"


def test_compare_retention_and_orphan_prune(tmp_path):
    store = RunStore(data_dir=tmp_path)
    run_id = "run_keep"
    base_meta = {
        "schema_version": 1,
        "product": "auditwalk",
        "product_version": "0.1.0",
        "run_id": run_id,
        "created_utc": 1,
        "mode": "quick",
        "host": {"hostname": "test-host", "os_family": "linux", "arch": "x86_64"},
        "collector_status": {
            "system": "ok",
            "disk": "ok",
            "processes": "ok",
            "network": "ok",
            "persistence": "ok",
            "installed": "skipped",
            "files": "ok",
            "firewall": "skipped",
        },
        "options": {
            "roots": ["/tmp"],
            "exclude": [],
            "hash": False,
            "max_files": 100,
            "max_procs": 10,
            "timeout_seconds": 60,
        },
    }
    store.create_run(run_id, {"meta": base_meta}, mode="quick")

    d1 = store.compares_dir / "diff_run_keep__run_keep.json"
    d1.write_text('{"left_run_id":"run_keep","right_run_id":"run_keep"}', encoding="utf-8")
    d2 = store.compares_dir / "diff_missing__run_keep.json"
    d2.write_text('{"left_run_id":"run_missing","right_run_id":"run_keep"}', encoding="utf-8")
    d3 = store.compares_dir / "diff_run_keep__missing.json"
    d3.write_text('{"left_run_id":"run_keep","right_run_id":"run_missing"}', encoding="utf-8")

    pruned = store.prune_orphan_compares()
    assert pruned["deleted"] == 2
    assert pruned["kept"] == 1
    assert d1.exists()
    assert not d2.exists()
    assert not d3.exists()
    idx_after_prune = store._read_json(store.index_path)  # noqa: SLF001
    compares_after_prune = idx_after_prune.get("compares", [])
    assert len(compares_after_prune) == 1
    assert compares_after_prune[0]["left_run_id"] == "run_keep"
    assert compares_after_prune[0]["right_run_id"] == "run_keep"

    # Retention by max count should keep only one newest compare.
    d4 = store.compares_dir / "diff_run_keep__run_keep_2.json"
    d4.write_text('{"left_run_id":"run_keep","right_run_id":"run_keep"}', encoding="utf-8")
    kept = store.apply_compare_retention(max_compares=1, max_age_days=365)
    assert kept["kept"] == 1
    assert kept["deleted"] == 1
    idx_after_retention = store._read_json(store.index_path)  # noqa: SLF001
    assert len(idx_after_retention.get("compares", [])) == 1


def test_run_index_updates_preserve_compare_entries(tmp_path):
    store = RunStore(data_dir=tmp_path)
    run_id = "run_keep_index"
    meta = {
        "schema_version": 1,
        "product": "auditwalk",
        "product_version": "0.1.0",
        "run_id": run_id,
        "created_utc": 1,
        "mode": "quick",
        "host": {"hostname": "test-host", "os_family": "linux", "arch": "x86_64"},
        "collector_status": {
            "system": "ok",
            "disk": "ok",
            "processes": "ok",
            "network": "ok",
            "persistence": "ok",
            "installed": "skipped",
            "files": "ok",
            "firewall": "skipped",
        },
        "options": {
            "roots": ["/tmp"],
            "exclude": [],
            "hash": False,
            "max_files": 100,
            "max_procs": 10,
            "timeout_seconds": 60,
        },
    }
    store.create_run(run_id, {"meta": meta}, mode="quick")
    diff_path = store.compares_dir / "diff_run_keep_index__run_keep_index.json"
    diff_path.write_text(
        '{"compare_id":"diff_run_keep_index__run_keep_index","left_run_id":"run_keep_index","right_run_id":"run_keep_index"}',
        encoding="utf-8",
    )
    store.sync_compare_index()

    # Run-index writes should not drop compare index rows.
    store.save_score(run_id, {"total": 5, "tier": "Low", "breakdown": {}, "findings": []})
    idx_after_score = store._read_json(store.index_path)  # noqa: SLF001
    assert len(idx_after_score.get("compares", [])) == 1

    store.apply_retention(max_runs=100, max_age_days=365)
    idx_after_retention = store._read_json(store.index_path)  # noqa: SLF001
    assert len(idx_after_retention.get("compares", [])) == 1


def test_run_store_json_write_is_deterministic_sorted_keys(tmp_path):
    store = RunStore(data_dir=tmp_path)
    target = tmp_path / "ordered.json"
    store._atomic_write_json(target, {"z": 1, "a": 2, "m": 3})  # noqa: SLF001
    text = target.read_text(encoding="utf-8")
    assert text.find('"a"') < text.find('"m"') < text.find('"z"')
