import sys
sys.path.insert(0, "/Users/amajumder/Downloads/my research/QPPG")
from qppg_service.monitor import QppgMonitor


def test_get_labeling_queue_returns_most_uncertain():
    monitor = QppgMonitor()
    for chain_id, risk in [("c0", 0.1), ("c1", 0.5), ("c2", 0.51), ("c3", 0.9), ("c4", 0.48)]:
        monitor.register_chain_for_labeling(chain_id, risk)
    queue = monitor.get_labeling_queue(n=2)
    assert len(queue) == 2
    for item in queue:
        assert abs(item["risk"] - 0.5) <= 0.1


def test_submit_label_stores_and_removes_from_buffer():
    monitor = QppgMonitor()
    monitor.register_chain_for_labeling("c1", 0.5)
    monitor.submit_label("c1", 1, 0.5)
    assert len(monitor._labeled_buffer) == 1
    assert len(monitor._uncertainty_buffer) == 0


def test_partial_update_accumulates_and_recals():
    from qppg_service.label_free_scorer import AgentFingerprinter
    scorer = AgentFingerprinter()

    def fake_chain(n_steps):
        return {
            "question": "Q",
            "steps": [{"thought": "t", "action": "search", "observation": "o"}] * n_steps,
            "final_answer": "A",
            "finished": True,
        }

    initial = [fake_chain(i % 4 + 1) for i in range(20)]
    scorer.fit(initial)

    new_chains = [fake_chain(i % 3 + 1) for i in range(10)]
    scorer.partial_update(new_chains, recal_every=10)
    assert scorer._n_updates_since_recal == 0  # recalibrated, counter reset


def test_partial_update_skips_before_threshold():
    from qppg_service.label_free_scorer import AgentFingerprinter
    scorer = AgentFingerprinter()

    def fake_chain(n_steps):
        return {
            "question": "Q",
            "steps": [{"thought": "t", "action": "s", "observation": "o"}] * n_steps,
            "final_answer": "A",
            "finished": True,
        }

    scorer.fit([fake_chain(i % 4 + 1) for i in range(20)])
    scorer.partial_update([fake_chain(2), fake_chain(3)], recal_every=10)
    assert scorer._n_updates_since_recal == 2  # not recalibrated yet


def test_transfer_calibration_from():
    source = QppgMonitor()
    target = QppgMonitor()
    source._isotonic_x = [0.1, 0.3, 0.5, 0.7, 0.9]
    source._isotonic_y = [0.05, 0.25, 0.55, 0.75, 0.95]
    target.transfer_calibration_from(source, alpha=0.5)
    assert hasattr(target, "_transferred_calibration")
    assert target._transferred_calibration["alpha"] == 0.5
    assert len(target._transferred_calibration["y"]) == 5


def test_transfer_calibration_no_op_when_source_empty():
    source = QppgMonitor()
    target = QppgMonitor()
    target.transfer_calibration_from(source)
    assert not hasattr(target, "_transferred_calibration")


def test_discover_failure_modes_finds_empty_answer():
    from qppg_service.failure_taxonomy import FailureTaxonomist
    ft = FailureTaxonomist()
    chains = [
        {"steps": [], "final_answer": "", "risk": 0.9},
        {"steps": [{"action": "search", "observation": "x"}] * 8, "final_answer": "y", "risk": 0.8},
        {"steps": [{"action": "search", "observation": "a"}] * 2, "final_answer": "ok fine", "risk": 0.2},
    ]
    modes = ft.discover_failure_modes(chains, risk_threshold=0.7)
    assert isinstance(modes, list)
    assert len(modes) > 0
    mode_names = {m["mode"] for m in modes}
    assert "EMPTY_ANSWER" in mode_names or "EXCESSIVE_SEARCH" in mode_names


def test_discover_failure_modes_skips_low_risk():
    from qppg_service.failure_taxonomy import FailureTaxonomist
    ft = FailureTaxonomist()
    chains = [
        {"steps": [], "final_answer": "", "risk": 0.3},  # low risk — should be skipped
    ]
    modes = ft.discover_failure_modes(chains, risk_threshold=0.7)
    assert modes == []
