"""
oyun_teorisi.verification — Verification engine for the Game Theory Lens (Lens #5 of 7).

Provides checks and a convergence score (yakinlasma) for game-theoretic
models.  Each check returns ``(passed: bool, detail: str)`` for
integration with the framework quality apparatus (AGENTS.md §5).

Checks implemented:
  - check_station_validity:       All players have valid NefsMakam
  - check_payoff_transformation:  AX62 — payoffs change with station ascent
  - check_strategy_completeness:  Every player has at least one strategy
  - check_nash_consistency:       Nash equilibria are internally consistent
  - check_convergence_bound:      KV₄/T6 — no score ≥ 1.0
  - check_tesanud_infirad:        AX63 — composition mode properly assigned
  - check_independence:           KV₇ — players independently defined
  - check_transparency:           AX57 — model state fully inspectable

Governing axioms:
  AX62/N-5: Nefs Station Ascent (core axiom for this lens)
  AX63:     Tesanüd/İnfirâd asymmetry
  AX52:     Multiplicative gate
  KV₃:     Observer non-interference
  KV₄/T6:  Convergence bound 0 < C < 1
  KV₇:     Independence

KV₇ compliance: This module imports ONLY from oyun_teorisi.types
                 and the standard library.
"""

from __future__ import annotations

from typing import Dict, List, Optional, Tuple

from oyun_teorisi.types import (
    CompositionMode,
    GameModel,
    GameOutcome,
    NefsMakam,
    PayoffEntry,
    Player,
    Strategy,
    StrategyType,
    clamp_score,
)


# ---------------------------------------------------------------------------
# Individual verification checks
# ---------------------------------------------------------------------------

def check_station_validity(model: GameModel) -> Tuple[bool, str]:
    """All players must have a valid NefsMakam station.

    Returns (True, detail) when every registered player has a NefsMakam
    value from {EMMARE, LEVVAME, MULHIME, MUTMAINNE}.
    """
    if model is None:
        return False, "No model provided"
    if not model.players:
        return True, "No players to check (vacuously true)"

    for player in model.players:
        if not isinstance(player.makam, NefsMakam):
            return False, (
                f"Player '{player.name}' has invalid makam: {player.makam}"
            )

    makams = {p.makam for p in model.players}
    return True, (
        f"All {len(model.players)} player(s) have valid stations, "
        f"{len(makams)} distinct makam(s)"
    )


def check_payoff_transformation(model: GameModel) -> Tuple[bool, str]:
    """AX62/N-5: Payoff functions must change with station ascent.

    Checks that at least some strategy pairs show different payoffs at
    different stations.  Requires payoff data at ≥2 distinct stations.
    """
    if model is None:
        return False, "No model provided"
    if not model.payoffs:
        return True, "No payoffs to check (vacuously true)"

    stations = model.count_stations_covered()
    if stations < 2:
        return True, (
            f"Only {stations} station(s) with payoff data — "
            "cannot verify transformation"
        )

    transform_count = 0
    total_pairs = 0

    for player in model.players:
        my_strats = model.get_strategies(player.name)
        opp_strats: set = set()
        for p in model.players:
            if p.name != player.name:
                for s in model.get_strategies(p.name):
                    opp_strats.add(s.name)

        for my_s in my_strats:
            for opp_s in opp_strats:
                total_pairs += 1
                if model.payoff_transforms_with_station(
                    player.name, my_s.name, opp_s
                ):
                    transform_count += 1

    if total_pairs == 0:
        return True, "No strategy pairs to check"

    ratio = transform_count / total_pairs
    if ratio > 0:
        return True, (
            f"AX62: {transform_count}/{total_pairs} strategy pair(s) show "
            f"station-dependent payoff ({ratio:.0%})"
        )
    return False, (
        f"AX62: No payoff transformation detected across stations "
        f"(0/{total_pairs})"
    )


def check_strategy_completeness(model: GameModel) -> Tuple[bool, str]:
    """Every registered player must have at least one strategy."""
    if model is None:
        return False, "No model provided"
    if not model.players:
        return True, "No players to check (vacuously true)"

    for player in model.players:
        strats = model.get_strategies(player.name)
        if not strats:
            return False, f"Player '{player.name}' has no strategies"

    total_strats = sum(
        len(model.get_strategies(p.name)) for p in model.players
    )
    return True, (
        f"All {len(model.players)} player(s) have strategies "
        f"({total_strats} total)"
    )


def check_nash_consistency(model: GameModel) -> Tuple[bool, str]:
    """Nash equilibria, if any, must be internally consistent.

    Checks that every Nash outcome has finite, valid payoffs and
    that player/strategy counts match.
    """
    if model is None:
        return False, "No model provided"

    nash = model.find_nash_equilibria()
    if not nash:
        return True, "No Nash equilibria claimed (vacuously consistent)"

    for i, outcome in enumerate(nash):
        for j, payoff in enumerate(outcome.payoffs):
            if not isinstance(payoff, (int, float)):
                return False, (
                    f"Nash outcome #{i}: payoff[{j}] is not numeric"
                )
            # NaN check
            if payoff != payoff:
                return False, f"Nash outcome #{i}: payoff[{j}] is NaN"

    return True, f"{len(nash)} Nash equilibri(a) internally consistent"


def check_convergence_bound(model: GameModel) -> Tuple[bool, str]:
    """KV₄/T6: Convergence-type scores must be strictly < 1.0.

    Verifies that tesanud_strength (a convergence measure) does not
    reach or exceed 1.0.
    """
    if model is None:
        return False, "No model provided"

    ts = model.tesanud_strength()
    if ts >= 1.0:
        return False, (
            f"KV4/T6: tesanud_strength = {ts} ≥ 1.0 — "
            "violates convergence bound"
        )
    return True, f"KV4/T6: convergence bound satisfied (tesanud={ts:.4f})"


def check_tesanud_infirad(model: GameModel) -> Tuple[bool, str]:
    """AX63: Composition mode must be correctly classified.

    Checks that the composition mode is a valid CompositionMode and
    reports the current strength.
    """
    if model is None:
        return False, "No model provided"

    mode = model.composition_mode
    if not isinstance(mode, CompositionMode):
        return False, f"Invalid composition mode: {mode}"

    ts = model.tesanud_strength()
    nash_count = len(model.find_nash_equilibria())

    if mode == CompositionMode.TESANUD:
        return True, (
            f"AX63 TESANUD: {nash_count} confirming equilibri(a), "
            f"strength={ts:.4f}"
        )
    if mode == CompositionMode.INFIRAD:
        return True, f"AX63 INFIRAD: isolated findings, strength={ts:.4f}"
    return True, "AX63 INDEPENDENT: no composition claimed"


def check_independence(model: GameModel) -> Tuple[bool, str]:
    """KV₇: Each player must have independently defined game elements.

    Checks that every player has their own strategy set and, when
    payoff data exists, their own payoff entries.
    """
    if model is None:
        return False, "No model provided"
    if len(model.players) < 2:
        return True, (
            "Fewer than 2 players — independence trivially satisfied"
        )

    issues: List[str] = []
    for player in model.players:
        strats = model.get_strategies(player.name)
        entries = [e for e in model.payoffs if e.player_name == player.name]
        if not strats:
            issues.append(f"'{player.name}' has no strategies")
        if model.payoffs and not entries:
            issues.append(f"'{player.name}' has no payoff entries")

    if issues:
        return False, f"KV7: Independence issues — {'; '.join(issues)}"
    return True, (
        f"KV7: All {len(model.players)} player(s) independently defined"
    )


def check_transparency(model: GameModel) -> Tuple[bool, str]:
    """AX57: Model state must be fully inspectable via to_dict().

    Verifies that to_dict() succeeds and contains all required keys.
    """
    if model is None:
        return False, "No model provided"

    try:
        d = model.to_dict()
    except Exception as exc:
        return False, f"AX57: Serialisation failed — {exc}"

    required = {
        "name", "players", "strategies", "payoffs",
        "outcomes", "composition_mode",
    }
    missing = required - set(d.keys())
    if missing:
        return False, f"AX57: Missing keys in model dict: {missing}"

    return True, (
        f"AX57: Model state fully inspectable ({len(d)} top-level keys)"
    )


# ---------------------------------------------------------------------------
# Aggregate verification
# ---------------------------------------------------------------------------

def verify_all(
    model: Optional[GameModel] = None,
) -> Dict[str, Tuple[bool, str]]:
    """Run all 8 checks and return results keyed by check name.

    Keys: station_validity, payoff_transformation, strategy_completeness,
    nash_consistency, convergence_bound, tesanud_infirad, independence,
    transparency.
    """
    return {
        "station_validity": check_station_validity(model),
        "payoff_transformation": check_payoff_transformation(model),
        "strategy_completeness": check_strategy_completeness(model),
        "nash_consistency": check_nash_consistency(model),
        "convergence_bound": check_convergence_bound(model),
        "tesanud_infirad": check_tesanud_infirad(model),
        "independence": check_independence(model),
        "transparency": check_transparency(model),
    }


# ---------------------------------------------------------------------------
# Convergence score (yakinlasma)
# ---------------------------------------------------------------------------

def yakinlasma(model: Optional[GameModel] = None) -> float:
    """Compute convergence score for a game model.

    Weighted composite of the 8 verification checks:
      station_validity:       0.10
      payoff_transformation:  0.20   (AX62 — core axiom for this lens)
      strategy_completeness:  0.10
      nash_consistency:       0.15
      convergence_bound:      0.10   (KV₄/T6)
      tesanud_infirad:        0.15   (AX63)
      independence:           0.10   (KV₇)
      transparency:           0.10   (AX57)

    Returns a score in [0, 1) per T6/KV₄.
    """
    weights = {
        "station_validity": 0.10,
        "payoff_transformation": 0.20,
        "strategy_completeness": 0.10,
        "nash_consistency": 0.15,
        "convergence_bound": 0.10,
        "tesanud_infirad": 0.15,
        "independence": 0.10,
        "transparency": 0.10,
    }

    results = verify_all(model)
    score = sum(
        weights[k] * (1.0 if passed else 0.0)
        for k, (passed, _) in results.items()
    )
    return clamp_score(score)


# ---------------------------------------------------------------------------
# Framework summary (AX57 transparency)
# ---------------------------------------------------------------------------

def framework_summary() -> dict:
    """Return metadata about this lens for AX57 disclosure.

    Provides the lens name, number, faculty, domain, checked axioms and
    kavaid, capabilities, and the structural maximum score.
    """
    return {
        "lens": "OyunTeorisi",
        "lens_number": 5,
        "faculty": "Nefs",
        "domain": "Game theory // strategic interaction",
        "axioms_checked": ["AX62", "AX63"],
        "kavaid_checked": ["KV4", "KV7"],
        "capabilities": [
            "Station-dependent payoff analysis (AX62/N-5)",
            "Nash equilibrium detection",
            "Tesanüd/İnfirâd composition (AX63)",
            "Payoff transformation verification",
            "Strategic independence (KV7)",
        ],
        "max_score": 0.9999,
    }
