"""
tests/test_oyun_teorisi.py — Comprehensive tests for the Game Theory Lens.

Covers:
  - types.py:         NefsMakam, StrategyType, CompositionMode, Strategy,
                      Player, PayoffEntry, GameOutcome, GameModel, clamp_score
  - verification.py:  8 check functions, verify_all, yakinlasma, framework_summary
  - constraints.py:   _parse_game, 9 constraint factories + composite

Test count target: ≥ 130 tests.
"""

from __future__ import annotations

import json
import math
import unittest
from typing import Optional

from oyun_teorisi.types import (
    CompositionMode,
    GameModel,
    GameOutcome,
    MAKAM_ORDER,
    NefsMakam,
    PayoffEntry,
    Player,
    Strategy,
    StrategyType,
    clamp_score,
)
from oyun_teorisi.verification import (
    check_convergence_bound,
    check_independence,
    check_nash_consistency,
    check_payoff_transformation,
    check_station_validity,
    check_strategy_completeness,
    check_tesanud_infirad,
    check_transparency,
    framework_summary,
    verify_all,
    yakinlasma,
)
from oyun_teorisi.constraints import (
    _parse_game,
    game_convergence_bound,
    game_convergence_score,
    game_independence,
    game_transparency,
    nash_consistency as nash_consistency_constraint,
    payoff_transformation as payoff_transformation_constraint,
    station_validity as station_validity_constraint,
    strategy_completeness as strategy_completeness_constraint,
    tesanud_infirad_asymmetry,
    valid_game_entry,
)


# ===================================================================
# Helpers — build reusable game models
# ===================================================================

def _make_valid_game() -> GameModel:
    """Create a fully specified, AX62-compliant 2-player game.

    Players: alice (EMMARE), bob (MUTMAINNE)
    Strategies per player: 1 WORLDLY + 1 ALIGNMENT
    Payoffs at all 4 stations demonstrate AX62 transformation:
      - EMMARE: worldly dominates
      - MUTMAINNE: alignment dominates
    One Nash equilibrium registered.
    Composition mode: TESANUD.
    """
    model = GameModel(name="nefs_dilemma")

    model.add_player(Player(name="alice", makam=NefsMakam.EMMARE))
    model.add_player(Player(name="bob", makam=NefsMakam.MUTMAINNE))

    model.add_strategy("alice", Strategy("selfish", StrategyType.WORLDLY))
    model.add_strategy("alice", Strategy("altruistic", StrategyType.ALIGNMENT))
    model.add_strategy("bob", Strategy("exploit", StrategyType.WORLDLY))
    model.add_strategy("bob", Strategy("serve", StrategyType.ALIGNMENT))

    # Payoffs demonstrating AX62: worldly ↓ alignment ↑ across stations
    for makam, w_pay, a_pay in [
        (NefsMakam.EMMARE, 5.0, 1.0),
        (NefsMakam.LEVVAME, 4.0, 2.0),
        (NefsMakam.MULHIME, 2.0, 4.0),
        (NefsMakam.MUTMAINNE, 1.0, 5.0),
    ]:
        # Alice payoffs
        model.add_payoff(PayoffEntry("alice", makam, "selfish", "exploit", w_pay))
        model.add_payoff(PayoffEntry("alice", makam, "selfish", "serve", w_pay + 1))
        model.add_payoff(PayoffEntry("alice", makam, "altruistic", "exploit", a_pay - 0.5))
        model.add_payoff(PayoffEntry("alice", makam, "altruistic", "serve", a_pay))
        # Bob payoffs
        model.add_payoff(PayoffEntry("bob", makam, "exploit", "selfish", w_pay))
        model.add_payoff(PayoffEntry("bob", makam, "exploit", "altruistic", w_pay + 1))
        model.add_payoff(PayoffEntry("bob", makam, "serve", "selfish", a_pay - 0.5))
        model.add_payoff(PayoffEntry("bob", makam, "serve", "altruistic", a_pay))

    model.add_outcome(GameOutcome(
        player_names=("alice", "bob"),
        strategies=("selfish", "exploit"),
        payoffs=(5.0, 5.0),
        is_nash=True,
    ))
    model.add_outcome(GameOutcome(
        player_names=("alice", "bob"),
        strategies=("altruistic", "serve"),
        payoffs=(5.0, 5.0),
        is_nash=False,
    ))

    model.composition_mode = CompositionMode.TESANUD
    return model


def _game_to_json(model: GameModel) -> str:
    """Serialise a GameModel to a JSON string."""
    return json.dumps(model.to_dict())


# ===================================================================
# 1. clamp_score
# ===================================================================

class TestClampScore(unittest.TestCase):

    def test_clamp_negative(self):
        self.assertEqual(clamp_score(-5.0), 0.0)

    def test_clamp_zero(self):
        self.assertEqual(clamp_score(0.0), 0.0)

    def test_clamp_normal(self):
        self.assertAlmostEqual(clamp_score(0.5), 0.5)

    def test_clamp_upper_bound(self):
        self.assertEqual(clamp_score(1.0), 0.9999)

    def test_clamp_above_one(self):
        self.assertEqual(clamp_score(10.0), 0.9999)

    def test_clamp_just_below_one(self):
        self.assertAlmostEqual(clamp_score(0.9998), 0.9998)


# ===================================================================
# 2. Enumerations
# ===================================================================

class TestNefsMakam(unittest.TestCase):

    def test_four_stations(self):
        self.assertEqual(len(NefsMakam), 4)

    def test_ascending_values(self):
        self.assertTrue(
            NefsMakam.EMMARE.value
            < NefsMakam.LEVVAME.value
            < NefsMakam.MULHIME.value
            < NefsMakam.MUTMAINNE.value
        )

    def test_makam_order_constant(self):
        self.assertEqual(len(MAKAM_ORDER), 4)
        self.assertEqual(MAKAM_ORDER[0], NefsMakam.EMMARE)
        self.assertEqual(MAKAM_ORDER[-1], NefsMakam.MUTMAINNE)

    def test_access_by_name(self):
        self.assertEqual(NefsMakam["EMMARE"], NefsMakam.EMMARE)

    def test_access_by_value(self):
        self.assertEqual(NefsMakam(3), NefsMakam.MULHIME)


class TestStrategyType(unittest.TestCase):

    def test_three_types(self):
        self.assertEqual(len(StrategyType), 3)

    def test_values(self):
        self.assertEqual(StrategyType.WORLDLY.value, "worldly")
        self.assertEqual(StrategyType.ALIGNMENT.value, "alignment")
        self.assertEqual(StrategyType.MIXED.value, "mixed")


class TestCompositionMode(unittest.TestCase):

    def test_three_modes(self):
        self.assertEqual(len(CompositionMode), 3)

    def test_values(self):
        self.assertEqual(CompositionMode.TESANUD.value, "tesanud")
        self.assertEqual(CompositionMode.INFIRAD.value, "infirad")
        self.assertEqual(CompositionMode.INDEPENDENT.value, "independent")


# ===================================================================
# 3. Data classes
# ===================================================================

class TestStrategy(unittest.TestCase):

    def test_create_valid(self):
        s = Strategy("cooperate", StrategyType.ALIGNMENT, "help others")
        self.assertEqual(s.name, "cooperate")
        self.assertEqual(s.strategy_type, StrategyType.ALIGNMENT)
        self.assertEqual(s.description, "help others")

    def test_frozen(self):
        s = Strategy("x", StrategyType.MIXED)
        with self.assertRaises(AttributeError):
            s.name = "y"  # type: ignore

    def test_empty_name_raises(self):
        with self.assertRaises(ValueError):
            Strategy("", StrategyType.WORLDLY)

    def test_whitespace_name_raises(self):
        with self.assertRaises(ValueError):
            Strategy("   ", StrategyType.WORLDLY)

    def test_default_description(self):
        s = Strategy("x", StrategyType.MIXED)
        self.assertEqual(s.description, "")


class TestPlayer(unittest.TestCase):

    def test_create_valid(self):
        p = Player("alice", NefsMakam.EMMARE)
        self.assertEqual(p.name, "alice")
        self.assertEqual(p.makam, NefsMakam.EMMARE)

    def test_frozen(self):
        p = Player("a", NefsMakam.LEVVAME)
        with self.assertRaises(AttributeError):
            p.name = "b"  # type: ignore

    def test_empty_name_raises(self):
        with self.assertRaises(ValueError):
            Player("", NefsMakam.EMMARE)

    def test_invalid_makam_type_raises(self):
        with self.assertRaises(ValueError):
            Player("x", "EMMARE")  # type: ignore


class TestPayoffEntry(unittest.TestCase):

    def test_create_valid(self):
        e = PayoffEntry("alice", NefsMakam.EMMARE, "coop", "defect", 3.5)
        self.assertEqual(e.player_name, "alice")
        self.assertEqual(e.makam, NefsMakam.EMMARE)
        self.assertAlmostEqual(e.payoff, 3.5)

    def test_frozen(self):
        e = PayoffEntry("a", NefsMakam.EMMARE, "x", "y", 1.0)
        with self.assertRaises(AttributeError):
            e.payoff = 2.0  # type: ignore

    def test_empty_player_name_raises(self):
        with self.assertRaises(ValueError):
            PayoffEntry("", NefsMakam.EMMARE, "x", "y", 1.0)

    def test_integer_payoff_accepted(self):
        e = PayoffEntry("a", NefsMakam.EMMARE, "x", "y", 5)
        self.assertEqual(e.payoff, 5)

    def test_negative_payoff_accepted(self):
        e = PayoffEntry("a", NefsMakam.EMMARE, "x", "y", -2.0)
        self.assertAlmostEqual(e.payoff, -2.0)


class TestGameOutcome(unittest.TestCase):

    def test_create_valid(self):
        o = GameOutcome(("a", "b"), ("s1", "s2"), (1.0, 2.0), is_nash=True)
        self.assertEqual(o.player_names, ("a", "b"))
        self.assertTrue(o.is_nash)

    def test_default_not_nash(self):
        o = GameOutcome(("a", "b"), ("s1", "s2"), (1.0, 2.0))
        self.assertFalse(o.is_nash)

    def test_length_mismatch_strategies_raises(self):
        with self.assertRaises(ValueError):
            GameOutcome(("a", "b"), ("s1",), (1.0, 2.0))

    def test_length_mismatch_payoffs_raises(self):
        with self.assertRaises(ValueError):
            GameOutcome(("a", "b"), ("s1", "s2"), (1.0,))

    def test_single_player_raises(self):
        with self.assertRaises(ValueError):
            GameOutcome(("a",), ("s1",), (1.0,))

    def test_three_players_valid(self):
        o = GameOutcome(("a", "b", "c"), ("s1", "s2", "s3"), (1, 2, 3))
        self.assertEqual(len(o.player_names), 3)


# ===================================================================
# 4. GameModel
# ===================================================================

class TestGameModel(unittest.TestCase):

    def test_create_empty(self):
        m = GameModel()
        self.assertEqual(m.name, "")
        self.assertEqual(m.players, [])
        self.assertEqual(m.payoffs, [])
        self.assertEqual(m.outcomes, [])
        self.assertEqual(m.composition_mode, CompositionMode.INDEPENDENT)

    def test_add_player(self):
        m = GameModel("g")
        m.add_player(Player("alice", NefsMakam.EMMARE))
        self.assertEqual(len(m.players), 1)
        self.assertIsNotNone(m.get_player("alice"))

    def test_add_player_wrong_type(self):
        m = GameModel()
        with self.assertRaises(TypeError):
            m.add_player("not a player")  # type: ignore

    def test_add_strategy(self):
        m = GameModel()
        m.add_player(Player("a", NefsMakam.EMMARE))
        m.add_strategy("a", Strategy("s1", StrategyType.WORLDLY))
        self.assertEqual(len(m.get_strategies("a")), 1)

    def test_add_strategy_unregistered_player_raises(self):
        m = GameModel()
        with self.assertRaises(KeyError):
            m.add_strategy("ghost", Strategy("s", StrategyType.MIXED))

    def test_duplicate_strategy_ignored(self):
        m = GameModel()
        m.add_player(Player("a", NefsMakam.EMMARE))
        s = Strategy("s1", StrategyType.WORLDLY)
        m.add_strategy("a", s)
        m.add_strategy("a", s)
        self.assertEqual(len(m.get_strategies("a")), 1)

    def test_add_payoff(self):
        m = GameModel()
        m.add_player(Player("a", NefsMakam.EMMARE))
        m.add_payoff(PayoffEntry("a", NefsMakam.EMMARE, "s", "o", 3.0))
        self.assertEqual(len(m.payoffs), 1)

    def test_add_payoff_unregistered_player_raises(self):
        m = GameModel()
        with self.assertRaises(KeyError):
            m.add_payoff(PayoffEntry("ghost", NefsMakam.EMMARE, "s", "o", 1.0))

    def test_get_payoff_found(self):
        m = GameModel()
        m.add_player(Player("a", NefsMakam.EMMARE))
        m.add_payoff(PayoffEntry("a", NefsMakam.EMMARE, "s", "o", 7.0))
        self.assertAlmostEqual(
            m.get_payoff("a", NefsMakam.EMMARE, "s", "o"), 7.0
        )

    def test_get_payoff_not_found(self):
        m = GameModel()
        m.add_player(Player("a", NefsMakam.EMMARE))
        self.assertIsNone(m.get_payoff("a", NefsMakam.EMMARE, "x", "y"))

    def test_get_payoffs_at_station(self):
        m = GameModel()
        m.add_player(Player("a", NefsMakam.EMMARE))
        m.add_payoff(PayoffEntry("a", NefsMakam.EMMARE, "s1", "o", 1.0))
        m.add_payoff(PayoffEntry("a", NefsMakam.EMMARE, "s2", "o", 2.0))
        m.add_payoff(PayoffEntry("a", NefsMakam.LEVVAME, "s1", "o", 3.0))
        self.assertEqual(len(m.get_payoffs_at_station("a", NefsMakam.EMMARE)), 2)

    def test_payoff_transforms_with_station(self):
        m = _make_valid_game()
        self.assertTrue(
            m.payoff_transforms_with_station("alice", "selfish", "exploit")
        )

    def test_payoff_no_transform_single_station(self):
        m = GameModel()
        m.add_player(Player("a", NefsMakam.EMMARE))
        m.add_player(Player("b", NefsMakam.LEVVAME))
        m.add_strategy("a", Strategy("s", StrategyType.MIXED))
        m.add_strategy("b", Strategy("t", StrategyType.MIXED))
        m.add_payoff(PayoffEntry("a", NefsMakam.EMMARE, "s", "t", 5.0))
        self.assertFalse(m.payoff_transforms_with_station("a", "s", "t"))

    def test_station_ascent_ratio_fully_compliant(self):
        m = _make_valid_game()
        ratio = m.station_ascent_ratio("alice")
        self.assertGreater(ratio, 0.0)

    def test_station_ascent_ratio_no_strategies(self):
        m = GameModel()
        m.add_player(Player("a", NefsMakam.EMMARE))
        self.assertAlmostEqual(m.station_ascent_ratio("a"), 0.0)

    def test_find_nash_equilibria(self):
        m = _make_valid_game()
        nash = m.find_nash_equilibria()
        self.assertEqual(len(nash), 1)
        self.assertTrue(nash[0].is_nash)

    def test_add_outcome(self):
        m = GameModel()
        m.add_player(Player("a", NefsMakam.EMMARE))
        m.add_player(Player("b", NefsMakam.LEVVAME))
        o = GameOutcome(("a", "b"), ("s1", "s2"), (1.0, 2.0))
        m.add_outcome(o)
        self.assertEqual(len(m.outcomes), 1)

    def test_composition_mode_set_get(self):
        m = GameModel()
        m.composition_mode = CompositionMode.TESANUD
        self.assertEqual(m.composition_mode, CompositionMode.TESANUD)

    def test_composition_mode_invalid_type(self):
        m = GameModel()
        with self.assertRaises(TypeError):
            m.composition_mode = "tesanud"  # type: ignore

    def test_tesanud_strength_empty(self):
        m = GameModel()
        self.assertAlmostEqual(m.tesanud_strength(), 0.0)

    def test_tesanud_strength_tesanud_mode(self):
        m = _make_valid_game()
        ts = m.tesanud_strength()
        self.assertGreater(ts, 0.0)
        self.assertLess(ts, 1.0)

    def test_tesanud_strength_infirad_mode(self):
        m = _make_valid_game()
        m.composition_mode = CompositionMode.INFIRAD
        ts = m.tesanud_strength()
        self.assertAlmostEqual(ts, clamp_score(0.1))

    def test_tesanud_strength_independent_mode(self):
        m = _make_valid_game()
        m.composition_mode = CompositionMode.INDEPENDENT
        ts = m.tesanud_strength()
        self.assertGreater(ts, 0.0)
        self.assertLess(ts, 1.0)

    def test_tesanud_strength_no_nash_in_tesanud(self):
        m = GameModel()
        m.add_player(Player("a", NefsMakam.EMMARE))
        m.add_player(Player("b", NefsMakam.LEVVAME))
        m.add_outcome(GameOutcome(("a", "b"), ("s1", "s2"), (1.0, 2.0), is_nash=False))
        m.composition_mode = CompositionMode.TESANUD
        self.assertAlmostEqual(m.tesanud_strength(), 0.0)

    def test_count_stations_covered(self):
        m = _make_valid_game()
        self.assertEqual(m.count_stations_covered(), 4)

    def test_count_stations_covered_empty(self):
        m = GameModel()
        self.assertEqual(m.count_stations_covered(), 0)

    def test_to_dict_keys(self):
        m = _make_valid_game()
        d = m.to_dict()
        expected = {"name", "players", "strategies", "payoffs", "outcomes", "composition_mode"}
        self.assertEqual(set(d.keys()), expected)

    def test_to_dict_roundtrip(self):
        m = _make_valid_game()
        d = m.to_dict()
        j = json.dumps(d)
        self.assertIsInstance(json.loads(j), dict)

    def test_get_player_not_found(self):
        m = GameModel()
        self.assertIsNone(m.get_player("ghost"))

    def test_get_strategies_empty(self):
        m = GameModel()
        self.assertEqual(m.get_strategies("no_one"), [])


# ===================================================================
# 5. Verification checks
# ===================================================================

class TestCheckStationValidity(unittest.TestCase):

    def test_valid_model(self):
        passed, _ = check_station_validity(_make_valid_game())
        self.assertTrue(passed)

    def test_none_model(self):
        passed, _ = check_station_validity(None)
        self.assertFalse(passed)

    def test_empty_model_vacuous(self):
        passed, detail = check_station_validity(GameModel())
        self.assertTrue(passed)
        self.assertIn("vacuously", detail)


class TestCheckPayoffTransformation(unittest.TestCase):

    def test_valid_transformation(self):
        passed, detail = check_payoff_transformation(_make_valid_game())
        self.assertTrue(passed)
        self.assertIn("AX62", detail)

    def test_none_model(self):
        passed, _ = check_payoff_transformation(None)
        self.assertFalse(passed)

    def test_no_payoffs_vacuous(self):
        m = GameModel()
        m.add_player(Player("a", NefsMakam.EMMARE))
        passed, detail = check_payoff_transformation(m)
        self.assertTrue(passed)
        self.assertIn("vacuously", detail)

    def test_single_station_vacuous(self):
        m = GameModel()
        m.add_player(Player("a", NefsMakam.EMMARE))
        m.add_player(Player("b", NefsMakam.LEVVAME))
        m.add_strategy("a", Strategy("s", StrategyType.MIXED))
        m.add_strategy("b", Strategy("t", StrategyType.MIXED))
        m.add_payoff(PayoffEntry("a", NefsMakam.EMMARE, "s", "t", 5.0))
        m.add_payoff(PayoffEntry("b", NefsMakam.EMMARE, "t", "s", 3.0))
        passed, detail = check_payoff_transformation(m)
        self.assertTrue(passed)
        self.assertIn("1 station", detail)

    def test_no_transformation_fails(self):
        m = GameModel()
        m.add_player(Player("a", NefsMakam.EMMARE))
        m.add_player(Player("b", NefsMakam.LEVVAME))
        m.add_strategy("a", Strategy("s", StrategyType.MIXED))
        m.add_strategy("b", Strategy("t", StrategyType.MIXED))
        # Same payoff at two stations — no transformation
        m.add_payoff(PayoffEntry("a", NefsMakam.EMMARE, "s", "t", 5.0))
        m.add_payoff(PayoffEntry("a", NefsMakam.MUTMAINNE, "s", "t", 5.0))
        m.add_payoff(PayoffEntry("b", NefsMakam.EMMARE, "t", "s", 3.0))
        m.add_payoff(PayoffEntry("b", NefsMakam.MUTMAINNE, "t", "s", 3.0))
        passed, detail = check_payoff_transformation(m)
        self.assertFalse(passed)
        self.assertIn("No payoff transformation", detail)


class TestCheckStrategyCompleteness(unittest.TestCase):

    def test_complete(self):
        passed, _ = check_strategy_completeness(_make_valid_game())
        self.assertTrue(passed)

    def test_none_model(self):
        passed, _ = check_strategy_completeness(None)
        self.assertFalse(passed)

    def test_missing_strategy_fails(self):
        m = GameModel()
        m.add_player(Player("a", NefsMakam.EMMARE))
        passed, detail = check_strategy_completeness(m)
        self.assertFalse(passed)
        self.assertIn("no strategies", detail)


class TestCheckNashConsistency(unittest.TestCase):

    def test_consistent(self):
        passed, _ = check_nash_consistency(_make_valid_game())
        self.assertTrue(passed)

    def test_no_nash_vacuous(self):
        m = GameModel()
        passed, detail = check_nash_consistency(m)
        self.assertTrue(passed)
        self.assertIn("vacuously", detail)

    def test_none_model(self):
        passed, _ = check_nash_consistency(None)
        self.assertFalse(passed)

    def test_nan_payoff_fails(self):
        m = GameModel()
        m.add_player(Player("a", NefsMakam.EMMARE))
        m.add_player(Player("b", NefsMakam.LEVVAME))
        m.add_outcome(GameOutcome(
            ("a", "b"), ("s1", "s2"),
            (float("nan"), 1.0), is_nash=True,
        ))
        passed, detail = check_nash_consistency(m)
        self.assertFalse(passed)
        self.assertIn("NaN", detail)


class TestCheckConvergenceBound(unittest.TestCase):

    def test_valid(self):
        passed, _ = check_convergence_bound(_make_valid_game())
        self.assertTrue(passed)

    def test_none_model(self):
        passed, _ = check_convergence_bound(None)
        self.assertFalse(passed)


class TestCheckTesanudInfirad(unittest.TestCase):

    def test_tesanud_mode(self):
        m = _make_valid_game()
        passed, detail = check_tesanud_infirad(m)
        self.assertTrue(passed)
        self.assertIn("TESANUD", detail)

    def test_infirad_mode(self):
        m = _make_valid_game()
        m.composition_mode = CompositionMode.INFIRAD
        passed, detail = check_tesanud_infirad(m)
        self.assertTrue(passed)
        self.assertIn("INFIRAD", detail)

    def test_independent_mode(self):
        m = _make_valid_game()
        m.composition_mode = CompositionMode.INDEPENDENT
        passed, detail = check_tesanud_infirad(m)
        self.assertTrue(passed)
        self.assertIn("INDEPENDENT", detail)

    def test_none_model(self):
        passed, _ = check_tesanud_infirad(None)
        self.assertFalse(passed)


class TestCheckIndependence(unittest.TestCase):

    def test_independent(self):
        passed, _ = check_independence(_make_valid_game())
        self.assertTrue(passed)

    def test_none_model(self):
        passed, _ = check_independence(None)
        self.assertFalse(passed)

    def test_single_player_trivial(self):
        m = GameModel()
        m.add_player(Player("a", NefsMakam.EMMARE))
        passed, detail = check_independence(m)
        self.assertTrue(passed)
        self.assertIn("trivially", detail)

    def test_missing_payoff_entries_fails(self):
        m = GameModel()
        m.add_player(Player("a", NefsMakam.EMMARE))
        m.add_player(Player("b", NefsMakam.LEVVAME))
        m.add_strategy("a", Strategy("s", StrategyType.MIXED))
        m.add_strategy("b", Strategy("t", StrategyType.MIXED))
        # Only player 'a' has payoffs
        m.add_payoff(PayoffEntry("a", NefsMakam.EMMARE, "s", "t", 5.0))
        passed, detail = check_independence(m)
        self.assertFalse(passed)
        self.assertIn("KV7", detail)


class TestCheckTransparency(unittest.TestCase):

    def test_valid(self):
        passed, _ = check_transparency(_make_valid_game())
        self.assertTrue(passed)

    def test_none_model(self):
        passed, _ = check_transparency(None)
        self.assertFalse(passed)

    def test_empty_model_still_transparent(self):
        passed, _ = check_transparency(GameModel())
        self.assertTrue(passed)


# ===================================================================
# 6. verify_all and yakinlasma
# ===================================================================

class TestVerifyAll(unittest.TestCase):

    def test_returns_all_eight_keys(self):
        results = verify_all(_make_valid_game())
        expected_keys = {
            "station_validity", "payoff_transformation",
            "strategy_completeness", "nash_consistency",
            "convergence_bound", "tesanud_infirad",
            "independence", "transparency",
        }
        self.assertEqual(set(results.keys()), expected_keys)

    def test_all_pass_for_valid_game(self):
        results = verify_all(_make_valid_game())
        for key, (passed, _) in results.items():
            self.assertTrue(passed, f"{key} should pass")

    def test_none_model_some_fail(self):
        results = verify_all(None)
        # At least station_validity should fail
        self.assertFalse(results["station_validity"][0])


class TestYakinlasma(unittest.TestCase):

    def test_valid_game_high_score(self):
        score = yakinlasma(_make_valid_game())
        self.assertGreater(score, 0.5)
        self.assertLess(score, 1.0)

    def test_score_always_below_one(self):
        score = yakinlasma(_make_valid_game())
        self.assertLess(score, 1.0)

    def test_none_model_low_score(self):
        score = yakinlasma(None)
        self.assertLess(score, 0.5)

    def test_empty_model_moderate_score(self):
        score = yakinlasma(GameModel())
        # Empty model passes some checks vacuously
        self.assertGreaterEqual(score, 0.0)
        self.assertLess(score, 1.0)

    def test_valid_game_and_empty_both_bounded(self):
        """Both valid and empty models must be < 1.0 (T6/KV₄).
        Empty model passes vacuously — this is acceptable per bayes_analiz precedent.
        """
        valid_score = yakinlasma(_make_valid_game())
        empty_score = yakinlasma(GameModel())
        self.assertLess(valid_score, 1.0)
        self.assertLess(empty_score, 1.0)


class TestFrameworkSummary(unittest.TestCase):

    def test_lens_name(self):
        s = framework_summary()
        self.assertEqual(s["lens"], "OyunTeorisi")

    def test_lens_number(self):
        s = framework_summary()
        self.assertEqual(s["lens_number"], 5)

    def test_faculty(self):
        s = framework_summary()
        self.assertEqual(s["faculty"], "Nefs")

    def test_max_score(self):
        s = framework_summary()
        self.assertAlmostEqual(s["max_score"], 0.9999)

    def test_axioms_include_ax62(self):
        s = framework_summary()
        self.assertIn("AX62", s["axioms_checked"])

    def test_kavaid_include_kv4(self):
        s = framework_summary()
        self.assertIn("KV4", s["kavaid_checked"])

    def test_capabilities_non_empty(self):
        s = framework_summary()
        self.assertGreater(len(s["capabilities"]), 0)


# ===================================================================
# 7. _parse_game
# ===================================================================

class TestParseGame(unittest.TestCase):

    def test_valid_json(self):
        m = _make_valid_game()
        ok, model, msg = _parse_game(_game_to_json(m))
        self.assertTrue(ok)
        self.assertIsNotNone(model)
        self.assertEqual(msg, "OK")

    def test_invalid_json(self):
        ok, model, msg = _parse_game("not json")
        self.assertFalse(ok)
        self.assertIsNone(model)

    def test_empty_object(self):
        ok, model, msg = _parse_game("{}")
        self.assertTrue(ok)
        self.assertIsNotNone(model)

    def test_non_dict_json(self):
        ok, model, msg = _parse_game("[1,2,3]")
        self.assertFalse(ok)
        self.assertIn("JSON object", msg)

    def test_bad_makam_raises(self):
        data = {"players": [{"name": "a", "makam": "INVALID"}]}
        ok, model, msg = _parse_game(json.dumps(data))
        self.assertFalse(ok)
        self.assertIn("construction error", msg)

    def test_numeric_makam_value(self):
        data = {
            "players": [{"name": "a", "makam": 1}, {"name": "b", "makam": 4}],
            "strategies": {
                "a": [{"name": "s", "type": "mixed"}],
                "b": [{"name": "t", "type": "mixed"}],
            },
        }
        ok, model, msg = _parse_game(json.dumps(data))
        self.assertTrue(ok)
        self.assertEqual(model.players[0].makam, NefsMakam.EMMARE)

    def test_roundtrip_preserves_structure(self):
        original = _make_valid_game()
        ok, rebuilt, _ = _parse_game(_game_to_json(original))
        self.assertTrue(ok)
        self.assertEqual(len(rebuilt.players), len(original.players))
        self.assertEqual(len(rebuilt.payoffs), len(original.payoffs))
        self.assertEqual(len(rebuilt.outcomes), len(original.outcomes))

    def test_none_input(self):
        ok, model, msg = _parse_game(None)  # type: ignore
        self.assertFalse(ok)


# ===================================================================
# 8. Constraint factories
# ===================================================================

class TestStationValidityConstraint(unittest.TestCase):

    def test_passes_valid(self):
        r = station_validity_constraint().check(_game_to_json(_make_valid_game()))
        self.assertTrue(r.passed)
        self.assertGreater(r.score, 0.0)

    def test_fails_invalid_json(self):
        r = station_validity_constraint().check("bad json")
        self.assertFalse(r.passed)
        self.assertAlmostEqual(r.score, 0.0)


class TestPayoffTransformationConstraint(unittest.TestCase):

    def test_passes_valid(self):
        r = payoff_transformation_constraint().check(_game_to_json(_make_valid_game()))
        self.assertTrue(r.passed)
        self.assertGreater(r.score, 0.5)

    def test_fails_bad_json(self):
        r = payoff_transformation_constraint().check("!!!")
        self.assertFalse(r.passed)


class TestStrategyCompletenessConstraint(unittest.TestCase):

    def test_passes_valid(self):
        r = strategy_completeness_constraint().check(_game_to_json(_make_valid_game()))
        self.assertTrue(r.passed)

    def test_fails_missing(self):
        m = GameModel()
        m.add_player(Player("a", NefsMakam.EMMARE))
        m.add_player(Player("b", NefsMakam.LEVVAME))
        r = strategy_completeness_constraint().check(_game_to_json(m))
        self.assertFalse(r.passed)


class TestNashConsistencyConstraint(unittest.TestCase):

    def test_passes_valid(self):
        r = nash_consistency_constraint().check(_game_to_json(_make_valid_game()))
        self.assertTrue(r.passed)


class TestGameConvergenceBoundConstraint(unittest.TestCase):

    def test_passes_valid(self):
        r = game_convergence_bound().check(_game_to_json(_make_valid_game()))
        self.assertTrue(r.passed)


class TestTesanudInfiradConstraint(unittest.TestCase):

    def test_passes_valid(self):
        r = tesanud_infirad_asymmetry().check(_game_to_json(_make_valid_game()))
        self.assertTrue(r.passed)


class TestGameIndependenceConstraint(unittest.TestCase):

    def test_passes_valid(self):
        r = game_independence().check(_game_to_json(_make_valid_game()))
        self.assertTrue(r.passed)


class TestGameTransparencyConstraint(unittest.TestCase):

    def test_passes_valid(self):
        r = game_transparency().check(_game_to_json(_make_valid_game()))
        self.assertTrue(r.passed)


class TestGameConvergenceScoreConstraint(unittest.TestCase):

    def test_score_below_one(self):
        r = game_convergence_score().check(_game_to_json(_make_valid_game()))
        self.assertTrue(r.passed)
        self.assertLess(r.score, 1.0)

    def test_bad_json(self):
        r = game_convergence_score().check("nope")
        self.assertFalse(r.passed)


class TestValidGameEntryConstraint(unittest.TestCase):

    def test_all_pass(self):
        r = valid_game_entry().check(_game_to_json(_make_valid_game()))
        self.assertTrue(r.passed)
        self.assertGreater(r.score, 0.5)

    def test_bad_json_fails(self):
        r = valid_game_entry().check("not json")
        self.assertFalse(r.passed)

    def test_incomplete_game_fails(self):
        m = GameModel()
        m.add_player(Player("a", NefsMakam.EMMARE))
        m.add_player(Player("b", NefsMakam.LEVVAME))
        # No strategies → strategy_completeness fails
        r = valid_game_entry().check(_game_to_json(m))
        self.assertFalse(r.passed)


# ===================================================================
# 9. Edge cases & cross-cutting
# ===================================================================

class TestEdgeCases(unittest.TestCase):

    def test_all_scores_below_one(self):
        """T6/KV₄: No score may reach 1.0."""
        m = _make_valid_game()
        self.assertLess(yakinlasma(m), 1.0)
        self.assertLess(m.tesanud_strength(), 1.0)

    def test_infirad_always_low(self):
        """AX63: In INFIRAD mode, strength is capped low."""
        m = _make_valid_game()
        m.composition_mode = CompositionMode.INFIRAD
        self.assertLessEqual(m.tesanud_strength(), 0.11)

    def test_three_player_game(self):
        m = GameModel("3p")
        m.add_player(Player("a", NefsMakam.EMMARE))
        m.add_player(Player("b", NefsMakam.LEVVAME))
        m.add_player(Player("c", NefsMakam.MUTMAINNE))
        m.add_strategy("a", Strategy("x", StrategyType.WORLDLY))
        m.add_strategy("b", Strategy("y", StrategyType.ALIGNMENT))
        m.add_strategy("c", Strategy("z", StrategyType.MIXED))
        passed, _ = check_station_validity(m)
        self.assertTrue(passed)

    def test_large_payoff_matrix(self):
        """Performance: a game with many payoff entries works."""
        m = GameModel("large")
        m.add_player(Player("a", NefsMakam.EMMARE))
        m.add_player(Player("b", NefsMakam.MUTMAINNE))
        for i in range(5):
            m.add_strategy("a", Strategy(f"a{i}", StrategyType.MIXED))
            m.add_strategy("b", Strategy(f"b{i}", StrategyType.MIXED))
        for makam in NefsMakam:
            for i in range(5):
                for j in range(5):
                    m.add_payoff(PayoffEntry("a", makam, f"a{i}", f"b{j}", float(i + j + makam.value)))
                    m.add_payoff(PayoffEntry("b", makam, f"b{j}", f"a{i}", float(i * j + makam.value)))
        passed, _ = check_payoff_transformation(m)
        self.assertTrue(passed)

    def test_makam_enum_iteration(self):
        """Iterate over all 4 stations."""
        names = [m.name for m in NefsMakam]
        self.assertEqual(names, ["EMMARE", "LEVVAME", "MULHIME", "MUTMAINNE"])

    def test_station_ascent_ratio_perfect(self):
        """A perfectly AX62-compliant game should have ratio > 0."""
        m = _make_valid_game()
        r = m.station_ascent_ratio("alice")
        self.assertGreater(r, 0.0)
        self.assertLessEqual(r, 1.0)

    def test_station_ascent_ratio_bob(self):
        """Bob's ascent ratio should also be > 0."""
        m = _make_valid_game()
        r = m.station_ascent_ratio("bob")
        self.assertGreater(r, 0.0)

    def test_multiple_nash_equilibria(self):
        m = _make_valid_game()
        m.add_outcome(GameOutcome(
            ("alice", "bob"), ("altruistic", "serve"), (5.0, 5.0), is_nash=True,
        ))
        self.assertEqual(len(m.find_nash_equilibria()), 2)

    def test_tesanud_strength_multiple_nash(self):
        m = _make_valid_game()
        # Add more nash outcomes
        m.add_outcome(GameOutcome(
            ("alice", "bob"), ("altruistic", "serve"), (5.0, 5.0), is_nash=True,
        ))
        m.composition_mode = CompositionMode.TESANUD
        ts = m.tesanud_strength()
        self.assertGreater(ts, 0.0)
        self.assertLess(ts, 1.0)


if __name__ == "__main__":
    unittest.main()
