"""tests/test_calibration_domain_adaptive.py — Unit tests for llm_guard.calibration.domain_adaptive_calibrator."""
import pytest
import numpy as np

pytestmark = pytest.mark.unit


class TestImport:
    def test_import(self):
        from llm_guard.calibration.domain_adaptive_calibrator import DomainAdaptiveCalibrator
        assert DomainAdaptiveCalibrator is not None


class TestInstantiation:
    def test_default_construction(self):
        from llm_guard.calibration.domain_adaptive_calibrator import DomainAdaptiveCalibrator
        cal = DomainAdaptiveCalibrator()
        assert cal._alpha == 0.10

    def test_invalid_noise_raises(self):
        from llm_guard.calibration.domain_adaptive_calibrator import DomainAdaptiveCalibrator
        with pytest.raises(ValueError):
            DomainAdaptiveCalibrator(pseudo_noise_estimate=-0.1)
        with pytest.raises(ValueError):
            DomainAdaptiveCalibrator(pseudo_noise_estimate=1.0)

    def test_get_threshold_before_fit_raises(self):
        from llm_guard.calibration.domain_adaptive_calibrator import DomainAdaptiveCalibrator
        cal = DomainAdaptiveCalibrator()
        with pytest.raises(RuntimeError):
            cal.get_threshold()


class TestFit:
    def _make_data(self, n=30, frac_correct=0.6):
        rng = np.random.default_rng(42)
        n_correct = int(n * frac_correct)
        scores = np.concatenate([rng.uniform(0.2, 0.5, n_correct),
                                  rng.uniform(0.6, 0.9, n - n_correct)])
        labels = [0] * n_correct + [1] * (n - n_correct)
        return scores.tolist(), labels

    def test_fit_returns_float(self):
        from llm_guard.calibration.domain_adaptive_calibrator import DomainAdaptiveCalibrator
        scores, labels = self._make_data()
        cal = DomainAdaptiveCalibrator()
        threshold = cal.fit(scores, labels)
        assert isinstance(threshold, float)
        assert 0.0 <= threshold <= 1.0

    def test_get_threshold_after_fit(self):
        from llm_guard.calibration.domain_adaptive_calibrator import DomainAdaptiveCalibrator
        scores, labels = self._make_data()
        cal = DomainAdaptiveCalibrator()
        t1 = cal.fit(scores, labels)
        t2 = cal.get_threshold()
        assert t1 == t2

    def test_fit_with_pseudo_labels(self):
        from llm_guard.calibration.domain_adaptive_calibrator import DomainAdaptiveCalibrator
        hard_scores, hard_labels = self._make_data(10)
        pseudo_scores, pseudo_labels = self._make_data(30)
        cal = DomainAdaptiveCalibrator(n_pseudo_min=20)
        threshold = cal.fit(hard_scores, hard_labels, pseudo_scores, pseudo_labels)
        assert isinstance(threshold, float)

    def test_fit_too_few_pseudo_uses_hard_only(self):
        from llm_guard.calibration.domain_adaptive_calibrator import DomainAdaptiveCalibrator
        hard_scores, hard_labels = self._make_data(10)
        pseudo_scores, pseudo_labels = self._make_data(5)  # fewer than n_pseudo_min=20
        cal = DomainAdaptiveCalibrator(n_pseudo_min=20)
        threshold = cal.fit(hard_scores, hard_labels, pseudo_scores, pseudo_labels)
        assert isinstance(threshold, float)

    def test_few_correct_uses_global_quantile(self):
        from llm_guard.calibration.domain_adaptive_calibrator import DomainAdaptiveCalibrator
        # All wrong → fewer than 5 correct → uses global quantile fallback
        scores = [0.7, 0.8, 0.9, 0.75, 0.85]
        labels = [1, 1, 1, 1, 1]
        cal = DomainAdaptiveCalibrator()
        threshold = cal.fit(scores, labels)
        assert isinstance(threshold, float)
