"""Tests for vote service engine functions: threshold details, session logic, nuanced evaluation, and simulation.

All tests are pure unit tests that exercise the engine functions directly
without any database dependency.

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)
"""

from __future__ import annotations

import math

import pytest

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


# ---------------------------------------------------------------------------
# Threshold details computation: real Forgeron case (97/23/7224)
# ---------------------------------------------------------------------------


class TestThresholdDetailsForgeron:
    """Simulate the threshold details computation that the service would return
    for the real Engagement Forgeron v2.0.0 vote."""

    WOT_SIZE = 7224
    VOTES_FOR = 97
    VOTES_AGAINST = 23
    TOTAL = 120
    SMITH_SIZE = 20
    SMITH_EXPONENT = 0.1
    TECHCOMM_SIZE = 5
    TECHCOMM_EXPONENT = 0.1

    def _compute_details(self) -> dict:
        """Reproduce the threshold details logic from vote_service.get_threshold_details."""
        wot_thresh = wot_threshold(
            wot_size=self.WOT_SIZE,
            total_votes=self.TOTAL,
            majority_pct=50,
            base_exponent=0.1,
            gradient_exponent=0.2,
            constant_base=0.0,
        )
        wot_passed = self.VOTES_FOR >= wot_thresh

        smith_thresh = smith_threshold(self.SMITH_SIZE, self.SMITH_EXPONENT)
        # Assume all smith members voted for
        smith_votes_for = 5
        smith_passed = smith_votes_for >= smith_thresh

        techcomm_thresh = techcomm_threshold(self.TECHCOMM_SIZE, self.TECHCOMM_EXPONENT)
        # Assume 2 techcomm members voted for
        techcomm_votes_for = 2
        techcomm_passed = techcomm_votes_for >= techcomm_thresh

        overall_passed = wot_passed and smith_passed and techcomm_passed
        participation_rate = self.TOTAL / self.WOT_SIZE

        return {
            "wot_threshold": wot_thresh,
            "smith_threshold": smith_thresh,
            "techcomm_threshold": techcomm_thresh,
            "votes_for": self.VOTES_FOR,
            "votes_against": self.VOTES_AGAINST,
            "votes_total": self.TOTAL,
            "wot_passed": wot_passed,
            "smith_passed": smith_passed,
            "techcomm_passed": techcomm_passed,
            "overall_passed": overall_passed,
            "participation_rate": round(participation_rate, 6),
            "formula_params": {
                "M": 50, "B": 0.1, "G": 0.2, "C": 0.0,
                "S": self.SMITH_EXPONENT, "T": self.TECHCOMM_EXPONENT,
            },
        }

    def test_wot_threshold_value(self):
        """WoT threshold for Forgeron vote should be in the expected range."""
        details = self._compute_details()
        assert 80 <= details["wot_threshold"] <= 120
        # 97 votes_for must pass
        assert details["wot_passed"] is True

    def test_smith_threshold_value(self):
        """Smith threshold: ceil(20^0.1) = ceil(1.35) = 2."""
        details = self._compute_details()
        assert details["smith_threshold"] == 2
        assert details["smith_passed"] is True

    def test_techcomm_threshold_value(self):
        """TechComm threshold: ceil(5^0.1) = ceil(1.175) = 2."""
        details = self._compute_details()
        assert details["techcomm_threshold"] == 2
        assert details["techcomm_passed"] is True

    def test_overall_pass(self):
        """All three criteria pass => overall adopted."""
        details = self._compute_details()
        assert details["overall_passed"] is True

    def test_participation_rate(self):
        """Participation rate for 120/7224 ~ 1.66%."""
        details = self._compute_details()
        expected = round(120 / 7224, 6)
        assert details["participation_rate"] == expected
        assert details["participation_rate"] < 0.02  # less than 2%

    def test_formula_params_present(self):
        """All formula params must be present and correct."""
        details = self._compute_details()
        params = details["formula_params"]
        assert params["M"] == 50
        assert params["B"] == 0.1
        assert params["G"] == 0.2
        assert params["C"] == 0.0
        assert params["S"] == 0.1
        assert params["T"] == 0.1


# ---------------------------------------------------------------------------
# Close session behavior (engine-level logic)
# ---------------------------------------------------------------------------


class TestCloseSessionLogic:
    """Test the logic that would execute when a session is closed:
    computing the final tally and determining adopted/rejected."""

    def test_binary_vote_adopted(self):
        """97 for / 23 against out of 7224 WoT => adopted."""
        threshold = wot_threshold(
            wot_size=7224,
            total_votes=120,
            majority_pct=50,
            base_exponent=0.1,
            gradient_exponent=0.2,
        )
        adopted = 97 >= threshold
        assert adopted is True
        result = "adopted" if adopted else "rejected"
        assert result == "adopted"

    def test_binary_vote_rejected(self):
        """50 for / 70 against out of 7224 WoT => rejected."""
        threshold = wot_threshold(
            wot_size=7224,
            total_votes=120,
            majority_pct=50,
            base_exponent=0.1,
            gradient_exponent=0.2,
        )
        adopted = 50 >= threshold
        assert adopted is False
        result = "adopted" if adopted else "rejected"
        assert result == "rejected"

    def test_close_with_zero_votes(self):
        """Session with 0 votes => threshold is ~0 (B^W), effectively rejected
        because 0 votes_for cannot meet even a tiny threshold."""
        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 -> effectively 0, ceil(0) = 0
        # But with 0 votes_for, 0 >= 0 is True
        # This is a degenerate case; in practice sessions with 0 votes
        # would be marked invalid
        assert threshold == 0 or threshold == math.ceil(0.1 ** 7224)

    def test_close_high_participation(self):
        """3000/7224 participating, 2500 for / 500 against => should pass.
        At ~41.5% participation with G=0.2, the inertia factor is low,
        so a strong supermajority should pass.
        """
        threshold = wot_threshold(
            wot_size=7224,
            total_votes=3000,
            majority_pct=50,
            base_exponent=0.1,
            gradient_exponent=0.2,
        )
        # At ~41.5% participation, threshold is lower than near-unanimity
        # but still above simple majority (1500)
        assert threshold > 1500, f"Threshold {threshold} should be above simple majority"
        assert threshold < 3000, f"Threshold {threshold} should be below total votes"
        adopted = 2500 >= threshold
        assert adopted is True

    def test_close_barely_fails(self):
        """Use exact threshold to verify borderline rejection."""
        threshold = wot_threshold(
            wot_size=7224,
            total_votes=120,
            majority_pct=50,
            base_exponent=0.1,
            gradient_exponent=0.2,
        )
        # votes_for = threshold - 1 => should fail
        barely_fail = (threshold - 1) >= threshold
        assert barely_fail is False

    def test_close_smith_criterion_blocks(self):
        """Even if WoT passes, failing Smith criterion blocks adoption."""
        threshold = wot_threshold(
            wot_size=7224,
            total_votes=120,
            majority_pct=50,
            base_exponent=0.1,
            gradient_exponent=0.2,
        )
        wot_pass = 97 >= threshold

        smith_required = smith_threshold(20, 0.1)  # ceil(20^0.1) = 2
        smith_ok = 1 >= smith_required  # Only 1 smith voted -> fails

        adopted = wot_pass and smith_ok
        assert wot_pass is True
        assert smith_ok is False
        assert adopted is False


# ---------------------------------------------------------------------------
# Nuanced vote result evaluation
# ---------------------------------------------------------------------------


class TestNuancedVoteResult:
    """Test nuanced vote evaluation with per-level breakdown,
    as would be returned by compute_result for nuanced votes."""

    def test_nuanced_adopted_with_breakdown(self):
        """Standard nuanced vote that passes threshold and min_participants."""
        votes = [5] * 30 + [4] * 20 + [3] * 15 + [2] * 5 + [1] * 3 + [0] * 2
        result = evaluate_nuanced(votes, threshold_pct=80, min_participants=59)

        # Total = 75, positive = 30+20+15 = 65
        assert result["total"] == 75
        assert result["positive_count"] == 65
        assert result["positive_pct"] == pytest.approx(86.67, abs=0.1)
        assert result["adopted"] is True

        # Verify per-level breakdown
        assert result["per_level_counts"][5] == 30
        assert result["per_level_counts"][4] == 20
        assert result["per_level_counts"][3] == 15
        assert result["per_level_counts"][2] == 5
        assert result["per_level_counts"][1] == 3
        assert result["per_level_counts"][0] == 2

    def test_nuanced_rejected_threshold_not_met(self):
        """Nuanced vote where positive percentage is below threshold."""
        votes = [5] * 10 + [4] * 10 + [3] * 10 + [2] * 15 + [1] * 15 + [0] * 10
        result = evaluate_nuanced(votes, threshold_pct=80, min_participants=59)

        # Total = 70, positive = 10+10+10 = 30
        assert result["total"] == 70
        assert result["positive_count"] == 30
        assert result["positive_pct"] == pytest.approx(42.86, abs=0.1)
        assert result["threshold_met"] is False
        assert result["min_participants_met"] is True
        assert result["adopted"] is False

    def test_nuanced_all_neutre(self):
        """All voters at level 3 (NEUTRE) still counts as positive."""
        votes = [3] * 60
        result = evaluate_nuanced(votes, threshold_pct=80, min_participants=59)

        assert result["total"] == 60
        assert result["positive_count"] == 60
        assert result["positive_pct"] == 100.0
        assert result["adopted"] is True

    def test_nuanced_mixed_heavy_negative(self):
        """Majority at levels 0-2 => rejected."""
        votes = [0] * 20 + [1] * 20 + [2] * 15 + [3] * 2 + [4] * 1 + [5] * 1
        result = evaluate_nuanced(votes, threshold_pct=80, min_participants=59)

        # Total = 59, positive = 2+1+1 = 4
        assert result["total"] == 59
        assert result["positive_count"] == 4
        assert result["positive_pct"] < 10
        assert result["adopted"] is False

    def test_nuanced_breakdown_dict_structure(self):
        """Verify the breakdown structure matches what the service builds."""
        votes = [5] * 20 + [4] * 20 + [3] * 19 + [2] * 5 + [1] * 3 + [0] * 2
        evaluation = evaluate_nuanced(votes, threshold_pct=80, min_participants=59)

        # Simulate the nuanced_breakdown dict the service builds
        nuanced_breakdown = {
            "per_level_counts": evaluation["per_level_counts"],
            "positive_count": evaluation["positive_count"],
            "positive_pct": evaluation["positive_pct"],
            "threshold_met": evaluation["threshold_met"],
            "min_participants_met": evaluation["min_participants_met"],
            "threshold_pct": 80,
            "min_participants": 59,
        }

        assert "per_level_counts" in nuanced_breakdown
        assert nuanced_breakdown["threshold_pct"] == 80
        assert nuanced_breakdown["min_participants"] == 59
        assert nuanced_breakdown["positive_count"] == 59
        assert nuanced_breakdown["threshold_met"] is True


# ---------------------------------------------------------------------------
# Simulation endpoint logic (pure engine functions)
# ---------------------------------------------------------------------------


class TestSimulationLogic:
    """Test the formula simulation that the /simulate endpoint performs,
    exercising all three threshold engine functions together."""

    def test_simulation_forgeron_case(self):
        """Simulate the Forgeron vote: wot=7224, total=120, M50 B.1 G.2."""
        wot_thresh = wot_threshold(
            wot_size=7224,
            total_votes=120,
            majority_pct=50,
            base_exponent=0.1,
            gradient_exponent=0.2,
        )

        # Derived values (matching the simulate endpoint logic)
        w = 7224
        t = 120
        m = 0.5
        g = 0.2
        participation_rate = t / w
        turnout_ratio = min(t / w, 1.0)
        inertia_factor = 1.0 - turnout_ratio ** g
        required_ratio = m + (1.0 - m) * inertia_factor

        assert 80 <= wot_thresh <= 120
        assert participation_rate == pytest.approx(0.016611, abs=0.001)
        assert 0 < inertia_factor < 1
        assert required_ratio > m  # Inertia pushes above simple majority

    def test_simulation_full_participation(self):
        """At 100% participation, threshold approaches simple majority."""
        wot_size = 100
        total = 100

        wot_thresh = wot_threshold(
            wot_size=wot_size,
            total_votes=total,
            majority_pct=50,
            base_exponent=0.1,
            gradient_exponent=0.2,
        )

        # At full participation, turnout_ratio = 1.0
        # inertia_factor = 1.0 - 1.0^0.2 = 0
        # required_ratio = 0.5 + 0.5*0 = 0.5
        # threshold = 0 + 0.1^100 + 0.5 * 100 = ~50
        assert wot_thresh == math.ceil(0.1 ** 100 + 0.5 * 100)
        assert wot_thresh == 50

    def test_simulation_with_smith_and_techcomm(self):
        """Simulate with all three criteria."""
        wot_thresh = wot_threshold(
            wot_size=7224,
            total_votes=120,
            majority_pct=50,
            base_exponent=0.1,
            gradient_exponent=0.2,
        )
        smith_thresh = smith_threshold(smith_wot_size=20, exponent=0.1)
        techcomm_thresh = techcomm_threshold(cotec_size=5, exponent=0.1)

        assert wot_thresh > 0
        assert smith_thresh == 2  # ceil(20^0.1) = ceil(1.35) = 2
        assert techcomm_thresh == 2  # ceil(5^0.1) = ceil(1.175) = 2

    def test_simulation_varying_majority(self):
        """Higher majority_pct increases the threshold at full participation."""
        thresh_50 = wot_threshold(wot_size=100, total_votes=100, majority_pct=50)
        thresh_66 = wot_threshold(wot_size=100, total_votes=100, majority_pct=66)
        thresh_80 = wot_threshold(wot_size=100, total_votes=100, majority_pct=80)

        assert thresh_50 < thresh_66 < thresh_80

    def test_simulation_varying_gradient(self):
        """Higher gradient exponent means more inertia at partial participation.

        With T/W < 1, a higher G makes (T/W)^G smaller, so
        inertia_factor = 1 - (T/W)^G becomes larger, raising the threshold.
        At 50% participation: (0.5)^0.2 ~ 0.87, (0.5)^1.0 = 0.5.
        """
        thresh_g02 = wot_threshold(
            wot_size=200, total_votes=100,
            majority_pct=50, gradient_exponent=0.2,
        )
        thresh_g10 = wot_threshold(
            wot_size=200, total_votes=100,
            majority_pct=50, gradient_exponent=1.0,
        )

        # Higher G = more inertia at partial participation = higher threshold
        assert thresh_g10 > thresh_g02

    def test_simulation_zero_votes(self):
        """Zero votes produces a minimal threshold."""
        thresh = wot_threshold(
            wot_size=7224,
            total_votes=0,
            majority_pct=50,
            base_exponent=0.1,
            gradient_exponent=0.2,
        )
        # B^W = 0.1^7224 ~ 0, ceil(0) = 0
        assert thresh == 0

    def test_simulation_small_wot(self):
        """Small WoT size (e.g. 5 members)."""
        thresh = wot_threshold(
            wot_size=5,
            total_votes=3,
            majority_pct=50,
            base_exponent=0.1,
            gradient_exponent=0.2,
        )
        # With 3/5 participation, should require more than simple majority of 3
        assert thresh >= 2
        assert thresh <= 3

    def test_simulation_result_structure(self):
        """Verify the simulation produces all expected values for the API response."""
        w = 7224
        t = 120
        m_pct = 50
        m = m_pct / 100.0
        b = 0.1
        g = 0.2
        c = 0.0

        wot_thresh = wot_threshold(
            wot_size=w, total_votes=t,
            majority_pct=m_pct, base_exponent=b,
            gradient_exponent=g, constant_base=c,
        )
        smith_thresh = smith_threshold(smith_wot_size=20, exponent=0.1)
        techcomm_thresh = techcomm_threshold(cotec_size=5, exponent=0.1)

        participation_rate = t / w
        turnout_ratio = min(t / w, 1.0)
        inertia_factor = 1.0 - turnout_ratio ** g
        required_ratio = m + (1.0 - m) * inertia_factor

        # Build the simulation result dict (matching FormulaSimulationResult schema)
        result = {
            "wot_threshold": wot_thresh,
            "smith_threshold": smith_thresh,
            "techcomm_threshold": techcomm_thresh,
            "participation_rate": round(participation_rate, 6),
            "required_ratio": round(required_ratio, 6),
            "inertia_factor": round(inertia_factor, 6),
        }

        assert isinstance(result["wot_threshold"], int)
        assert isinstance(result["smith_threshold"], int)
        assert isinstance(result["techcomm_threshold"], int)
        assert 0 < result["participation_rate"] < 1
        assert 0 < result["required_ratio"] <= 1
        assert 0 < result["inertia_factor"] < 1
