"""Tests for WoT threshold formula, Smith threshold, and TechComm threshold.

Real-world reference case:
    Vote Engagement Forgeron v2.0.0 (Feb 2026)
    wot_size=7224, votes_for=97, votes_against=23, total=120
    params M=50, B=0.1, G=0.2 => threshold=94 => adopted (97 >= 94)
"""

import math
import pytest

from app.engine.threshold import wot_threshold
from app.engine.smith_threshold import smith_threshold
from app.engine.techcomm_threshold import techcomm_threshold


# ---------------------------------------------------------------------------
# WoT threshold: real-world vote Forgeron
# ---------------------------------------------------------------------------

class TestWotThresholdForgeron:
    """Test with the actual Engagement Forgeron v2.0.0 vote numbers."""

    def test_forgeron_vote_passes(self):
        """97 votes_for out of 120 total (wot=7224) must pass."""
        threshold = wot_threshold(
            wot_size=7224,
            total_votes=120,
            majority_pct=50,
            base_exponent=0.1,
            gradient_exponent=0.2,
        )
        # With low participation (120/7224 ~ 1.66%), near-unanimity is required.
        # The historical threshold was 94, and 97 >= 94.
        assert 97 >= threshold
        # The threshold should be high relative to total votes (inertia effect)
        assert threshold > 60, f"Threshold {threshold} should be well above simple majority"

    def test_forgeron_vote_threshold_value(self):
        """Verify the computed threshold is in a reasonable range."""
        threshold = wot_threshold(
            wot_size=7224,
            total_votes=120,
            majority_pct=50,
            base_exponent=0.1,
            gradient_exponent=0.2,
        )
        # At ~1.66% participation, inertia should push threshold close to 78-95%
        # of total votes. The exact value depends on the formula.
        assert 80 <= threshold <= 120


# ---------------------------------------------------------------------------
# WoT threshold: low participation
# ---------------------------------------------------------------------------

class TestWotThresholdLowParticipation:
    """With very low participation, near-unanimity should be required."""

    def test_ten_votes_out_of_7224(self):
        """10 voters out of 7224 => nearly all must vote 'for'."""
        threshold = wot_threshold(
            wot_size=7224,
            total_votes=10,
            majority_pct=50,
            base_exponent=0.1,
            gradient_exponent=0.2,
        )
        # With participation ratio 10/7224 ~ 0.14%, threshold should be
        # very close to total_votes (near-unanimity).
        assert threshold >= 9, f"Expected near-unanimity but got threshold={threshold}"
        assert threshold <= 10


# ---------------------------------------------------------------------------
# WoT threshold: high participation
# ---------------------------------------------------------------------------

class TestWotThresholdHighParticipation:
    """With high participation, threshold should approach simple majority M."""

    def test_3000_votes_out_of_7224(self):
        """3000/7224 ~ 41.5% participation => threshold closer to 50%."""
        threshold = wot_threshold(
            wot_size=7224,
            total_votes=3000,
            majority_pct=50,
            base_exponent=0.1,
            gradient_exponent=0.2,
        )
        # With ~42% participation, the inertia factor diminishes.
        # threshold should be well below 90% of votes but above simple majority.
        simple_majority = math.ceil(3000 * 0.5)
        assert threshold >= simple_majority, (
            f"Threshold {threshold} should be at least simple majority {simple_majority}"
        )
        # Should be noticeably less than near-unanimity
        assert threshold < 2700, (
            f"Threshold {threshold} should be much less than near-unanimity at high participation"
        )


# ---------------------------------------------------------------------------
# WoT threshold: edge cases
# ---------------------------------------------------------------------------

class TestWotThresholdEdgeCases:
    """Edge-case behaviour."""

    def test_zero_total_votes(self):
        """With zero votes, threshold is ceil(C + B^W)."""
        threshold = wot_threshold(
            wot_size=7224,
            total_votes=0,
            majority_pct=50,
            base_exponent=0.1,
            gradient_exponent=0.2,
        )
        # B^W = 0.1^7224 is effectively 0
        expected = math.ceil(0.0 + 0.1 ** 7224)
        assert threshold == expected

    def test_invalid_wot_size_zero(self):
        with pytest.raises(ValueError, match="wot_size"):
            wot_threshold(wot_size=0, total_votes=10)

    def test_invalid_negative_votes(self):
        with pytest.raises(ValueError, match="total_votes"):
            wot_threshold(wot_size=100, total_votes=-1)

    def test_invalid_majority_pct(self):
        with pytest.raises(ValueError, match="majority_pct"):
            wot_threshold(wot_size=100, total_votes=10, majority_pct=150)


# ---------------------------------------------------------------------------
# Smith threshold
# ---------------------------------------------------------------------------

class TestSmithThreshold:
    """Test Smith sub-WoT threshold: ceil(smith_size ^ S)."""

    def test_smith_size_20_exponent_01(self):
        """smith_size=20, exponent=0.1 => ceil(20^0.1)."""
        result = smith_threshold(smith_wot_size=20, exponent=0.1)
        expected = math.ceil(20 ** 0.1)
        assert result == expected
        # 20^0.1 ~ 1.35, ceil => 2
        assert result == 2

    def test_smith_size_1(self):
        """smith_size=1 => ceil(1^0.1) = 1."""
        assert smith_threshold(smith_wot_size=1, exponent=0.1) == 1

    def test_smith_invalid(self):
        with pytest.raises(ValueError):
            smith_threshold(smith_wot_size=0)


# ---------------------------------------------------------------------------
# TechComm threshold
# ---------------------------------------------------------------------------

class TestTechcommThreshold:
    """Test TechComm threshold: ceil(cotec_size ^ T)."""

    def test_cotec_size_5_exponent_01(self):
        """cotec_size=5, exponent=0.1 => ceil(5^0.1)."""
        result = techcomm_threshold(cotec_size=5, exponent=0.1)
        expected = math.ceil(5 ** 0.1)
        assert result == expected
        # 5^0.1 ~ 1.175, ceil => 2
        assert result == 2

    def test_cotec_size_1(self):
        assert techcomm_threshold(cotec_size=1, exponent=0.1) == 1

    def test_cotec_invalid(self):
        with pytest.raises(ValueError):
            techcomm_threshold(cotec_size=0)
