"""A primitive_gap (or blocked pack install) must not let a codegen run finalize as
plain "completed" -- that's the same label as a run with nothing missing, which is a
semantic lie once a required capability wasn't fulfilled. `_select_final_status`
decides the run's terminal status; `_capability_gap_message` makes sure the owner
actually sees what wasn't fulfilled, not just a different status label. Both are pure
functions precisely so they're testable without mocking run_codegen's huge dependency
graph -- same reasoning that led to testing _cloud_entity_dict directly in the
Level 2 work rather than through the full install_pack chain."""
from __future__ import annotations

from src.workers.tasks.codegen import _capability_gap_message, _select_final_status


def test_no_blocked_packs_or_gaps_produces_no_message():
    assert _capability_gap_message([], []) == ""


def test_blocked_pack_names_slug_and_errors():
    blocked = [{"pack_slug": "contact-capture", "errors": ["missing_prerequisite:stripe"]}]
    msg = _capability_gap_message(blocked, [])
    assert "contact-capture" in msg
    assert "missing_prerequisite:stripe" in msg


def test_gap_names_requirement_missing_primitive_and_criticality():
    gaps = [{"name": "expense_report", "missing": ["approval.quorum"], "criticality": "required"}]
    msg = _capability_gap_message([], gaps)
    assert "expense_report" in msg
    assert "approval.quorum" in msg
    assert "required" in msg


def test_both_blocked_packs_and_gaps_both_appear():
    blocked = [{"pack_slug": "x", "errors": ["e"]}]
    gaps = [{"name": "y", "missing": ["z"], "criticality": "optional"}]
    msg = _capability_gap_message(blocked, gaps)
    assert "x" in msg and "e" in msg
    assert "y" in msg and "z" in msg and "optional" in msg


def test_pending_user_input_takes_priority_over_everything():
    assert _select_final_status(True, [{"pack_slug": "x"}], [{"criticality": "required"}]) == "user_input_required"


def test_required_gap_blocks():
    assert _select_final_status(False, [], [{"criticality": "required"}]) == "blocked"


def test_gap_missing_criticality_key_defaults_to_required_and_blocks():
    assert _select_final_status(False, [], [{}]) == "blocked"


def test_blocked_pack_install_alone_blocks_regardless_of_any_gaps_criticality():
    """A blocked catalog-pack install has no criticality concept of its own -- it's
    always treated as blocking, since it's a pack the project owner directly
    requested."""
    assert _select_final_status(False, [{"pack_slug": "x"}], [{"criticality": "optional"}]) == "blocked"


def test_only_optional_gaps_is_completed_with_limitations():
    assert _select_final_status(False, [], [{"criticality": "optional"}]) == "completed_with_limitations"


def test_nothing_blocked_or_gapped_is_plain_completed():
    assert _select_final_status(False, [], []) == "completed"


def test_mixed_required_and_optional_gaps_blocks():
    gaps = [{"criticality": "optional"}, {"criticality": "required"}]
    assert _select_final_status(False, [], gaps) == "blocked"


def test_configuration_required_pack_alone_is_completed_with_limitations_not_blocked():
    """A pack with status=configuration_required is ok=True (see service.py's
    install_pack) -- it must never land in blocked_packs, and must not force the run
    to "blocked" just because a connector isn't connected yet."""
    pending = [{"pack_slug": "payments", "status": "configuration_required"}]
    assert _select_final_status(False, [], [], pending) == "completed_with_limitations"


def test_configuration_required_does_not_escalate_a_required_gap_or_block():
    pending = [{"pack_slug": "payments", "status": "configuration_required"}]
    assert _select_final_status(False, [], [{"criticality": "required"}], pending) == "blocked"


def test_nothing_blocked_gapped_or_pending_is_plain_completed():
    assert _select_final_status(False, [], [], []) == "completed"


def test_pending_config_message_says_action_required_not_skipped():
    pending = [
        {
            "pack_slug": "payments",
            "blocker": {"pending_connections": ["stripe"]},
        }
    ]
    msg = _capability_gap_message([], [], pending)
    assert "payments" in msg
    assert "stripe" in msg
    assert "Action required" in msg
    assert "skipped" not in msg


def test_pending_config_and_real_gap_both_appear_in_separate_sections():
    blocked = [{"pack_slug": "x", "errors": ["e"]}]
    pending = [{"pack_slug": "payments", "blocker": {"pending_connections": ["stripe"]}}]
    msg = _capability_gap_message(blocked, [], pending)
    assert "x" in msg and "skipped rather than faked" in msg
    assert "payments" in msg and "Action required" in msg
