"""W21B-w3 Commit 3 — active-only audit helper.

A pure, read-only observability helper that partitions the current
background_render output into the *active* visual set (current expected
∩ checkpoint status=='ok') versus *stale* leftovers (asset rows / PNG
files that are not part of the active set). It performs NO production
behaviour: no DB write, no PNG create/delete, no checkpoint mutation —
the brief's no-destructive-cleanup lock. Stale is reported, never
cleaned. Tests are deterministic and assert the helper does not mutate
its inputs.
"""
from __future__ import annotations

import copy

from app.modules.pipeline.background_render_active_audit import (
    build_active_only_audit,
)


def _group(
    *,
    status="ok",
    is_reuse=False,
    render_action="render_new_plate",
    reuse_target_bg_id="",
    png_path="p.png",
):
    return {
        "status": status,
        "is_reuse": is_reuse,
        "render_action": render_action,
        "reuse_target_bg_id": reuse_target_bg_id,
        "png_path": png_path,
    }


def test_active_includes_ok_normal_and_ok_reuse_with_counts_split():
    """#1: status-ok normal + status-ok reuse are both active; reuse and
    new-render counts are split. The reuse bg has an alias asset (file
    present) but no PNG of its own on disk."""
    expected = ["L01B01", "L01B02"]
    groups = {
        "L01B01": _group(png_path="/img/L01B01.png"),
        "L01B02": _group(
            is_reuse=True,
            render_action="reuse_existing_plate",
            reuse_target_bg_id="L01B01",
            png_path="/img/L01B01.png",  # alias
        ),
    }
    out = build_active_only_audit(
        expected_bg_ids=expected,
        render_groups=groups,
        asset_bg_ids={"L01B01", "L01B02"},
        asset_bg_ids_with_files={"L01B01", "L01B02"},
        file_bg_ids={"L01B01"},  # reuse aliases — no own PNG
    )
    active_ids = [a["bg_id"] for a in out["active"]]
    assert active_ids == ["L01B01", "L01B02"]
    assert out["summary"]["active_count"] == 2
    assert out["summary"]["active_found_count"] == 2
    assert out["summary"]["reuse_count"] == 1
    assert out["summary"]["new_render_count"] == 1
    assert out["stale_asset_bg_ids"] == []
    assert out["stale_file_bg_ids"] == []
    reuse_item = next(a for a in out["active"] if a["bg_id"] == "L01B02")
    assert reuse_item["is_reuse"] is True
    assert reuse_item["reuse_target_bg_id"] == "L01B01"


def test_expected_but_failed_bg_drops_to_stale():
    """#2: a bg that is expected but checkpoint status != 'ok'
    (failed / reuse_target_missing) is NOT active; its leftover old
    asset row and PNG file fall to the stale side."""
    expected = ["L01B01", "L05B07"]
    groups = {
        "L01B01": _group(png_path="/img/L01B01.png"),
        "L05B07": _group(status="failed", png_path=""),
        "L09B02": _group(status="reuse_target_missing"),  # also expected below
    }
    expected = ["L01B01", "L05B07", "L09B02"]
    out = build_active_only_audit(
        expected_bg_ids=expected,
        render_groups=groups,
        asset_bg_ids={"L01B01", "L05B07", "L09B02"},
        asset_bg_ids_with_files={"L01B01", "L05B07", "L09B02"},
        file_bg_ids={"L01B01", "L05B07", "L09B02"},
    )
    assert [a["bg_id"] for a in out["active"]] == ["L01B01"]
    # failed + reuse_target_missing bgs' stale assets/files surface as stale.
    assert out["stale_asset_bg_ids"] == ["L05B07", "L09B02"]
    assert out["stale_file_bg_ids"] == ["L05B07", "L09B02"]
    assert out["summary"]["new_render_count"] == 1
    assert out["summary"]["reuse_count"] == 0


def test_active_but_missing_asset_or_file_is_active_missing_not_stale():
    """#3: an active (status ok) bg with no asset row / no resolvable
    file is an active-missing diagnostic, NOT stale."""
    expected = ["A", "B"]
    groups = {
        "A": _group(png_path="/img/A.png"),  # no asset row at all
        "B": _group(png_path="/img/B.png"),  # asset row but file missing
    }
    out = build_active_only_audit(
        expected_bg_ids=expected,
        render_groups=groups,
        asset_bg_ids={"B"},               # A has no asset row
        asset_bg_ids_with_files=set(),    # B's file does not resolve
        file_bg_ids=set(),
    )
    assert [a["bg_id"] for a in out["active"]] == ["A", "B"]
    assert out["summary"]["active_found_count"] == 0
    assert out["active_missing"] == ["A"]          # no asset row
    assert out["active_missing_files"] == ["B"]    # asset row, no file
    # Neither A nor B is stale — they are current/active, just incomplete.
    assert out["stale_asset_bg_ids"] == []
    assert out["stale_file_bg_ids"] == []


def test_expected_outside_asset_and_file_are_stale():
    """#4: asset rows / PNG files for bgs outside the active set are
    split into stale_asset_bg_ids / stale_file_bg_ids."""
    expected = ["A"]
    groups = {"A": _group(png_path="/img/A.png")}
    out = build_active_only_audit(
        expected_bg_ids=expected,
        render_groups=groups,
        asset_bg_ids={"A", "ZOLD"},        # ZOLD is a stale asset row
        asset_bg_ids_with_files={"A", "ZOLD"},
        file_bg_ids={"A", "YORPHAN"},      # YORPHAN is a stale PNG file
    )
    assert [a["bg_id"] for a in out["active"]] == ["A"]
    assert out["stale_asset_bg_ids"] == ["ZOLD"]
    assert out["stale_file_bg_ids"] == ["YORPHAN"]
    assert out["summary"]["stale_asset_count"] == 1
    assert out["summary"]["stale_file_count"] == 1


def test_helper_does_not_mutate_inputs():
    """#5: read-only — inputs are not mutated."""
    expected = ["A", "B"]
    groups = {
        "A": _group(png_path="/img/A.png"),
        "B": _group(
            is_reuse=True, render_action="reuse_existing_plate",
            reuse_target_bg_id="A", png_path="/img/A.png",
        ),
    }
    asset_bg_ids = {"A", "B", "ZOLD"}
    asset_with_files = {"A", "B"}
    file_bg_ids = {"A", "YORPHAN"}

    expected_copy = copy.deepcopy(expected)
    groups_copy = copy.deepcopy(groups)
    asset_copy = set(asset_bg_ids)
    awf_copy = set(asset_with_files)
    file_copy = set(file_bg_ids)

    build_active_only_audit(
        expected_bg_ids=expected,
        render_groups=groups,
        asset_bg_ids=asset_bg_ids,
        asset_bg_ids_with_files=asset_with_files,
        file_bg_ids=file_bg_ids,
    )
    assert expected == expected_copy
    assert groups == groups_copy
    assert asset_bg_ids == asset_copy
    assert asset_with_files == awf_copy
    assert file_bg_ids == file_copy
