"""
oyun_teorisi.constraints — ai_assert constraint factories for the Game Theory Lens.

Each factory returns an ``ai_assert.Constraint`` instance whose
``check_fn`` accepts a JSON string and returns ``(passed, score, message)``.

Constraints enforce:
  AX62/N-5: Nefs Station Ascent — payoff transformation across stations
  AX63:     Tesanüd/İnfirâd asymmetry — composition correctness
  AX52:     Multiplicative gate
  KV₄/T6:  Convergence bound (0 < C < 1)
  KV₇:     Independence — players independently defined
  AX57:     Transparency — model state must be inspectable

KV₇ compliance: This module imports ONLY from ai_assert and
                 oyun_teorisi.types / oyun_teorisi.verification,
                 plus the standard library.
"""

from __future__ import annotations

import json
from typing import Tuple

from ai_assert import Constraint

from oyun_teorisi.types import (
    CompositionMode,
    GameModel,
    GameOutcome,
    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,
    yakinlasma,
)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _parse_game(output: str) -> Tuple[bool, "GameModel | None", str]:
    """Parse a JSON string into a :class:`GameModel`.

    Expected JSON schema::

        {
            "name": str,
            "players": [{"name": str, "makam": str}, ...],
            "strategies": {
                "<player_name>": [
                    {"name": str, "type": str, "description": str}, ...
                ]
            },
            "payoffs": [
                {
                    "player": str, "makam": str,
                    "my_strategy": str, "opponent_strategy": str,
                    "payoff": float
                }, ...
            ],
            "outcomes": [
                {
                    "players": [str, ...], "strategies": [str, ...],
                    "payoffs": [float, ...], "is_nash": bool
                }, ...
            ],
            "composition_mode": str   // "tesanud" | "infirad" | "independent"
        }

    Returns:
        (success, model_or_None, message)
    """
    try:
        data = json.loads(output)
    except (json.JSONDecodeError, TypeError) as exc:
        return False, None, f"JSON parse error: {exc}"

    if not isinstance(data, dict):
        return False, None, "Expected a JSON object"

    try:
        model = GameModel(name=data.get("name", ""))

        # ---- players --------------------------------------------------
        for p in data.get("players", []):
            makam_raw = p.get("makam", "EMMARE")
            if isinstance(makam_raw, str):
                makam = NefsMakam[makam_raw]
            else:
                makam = NefsMakam(int(makam_raw))
            model.add_player(Player(name=p["name"], makam=makam))

        # ---- strategies -----------------------------------------------
        for pname, strats in data.get("strategies", {}).items():
            for s in strats:
                stype = StrategyType(s.get("type", "mixed"))
                model.add_strategy(
                    pname,
                    Strategy(
                        name=s["name"],
                        strategy_type=stype,
                        description=s.get("description", ""),
                    ),
                )

        # ---- payoffs --------------------------------------------------
        for e in data.get("payoffs", []):
            makam_raw = e.get("makam", "EMMARE")
            if isinstance(makam_raw, str):
                makam = NefsMakam[makam_raw]
            else:
                makam = NefsMakam(int(makam_raw))
            model.add_payoff(
                PayoffEntry(
                    player_name=e["player"],
                    makam=makam,
                    my_strategy=e["my_strategy"],
                    opponent_strategy=e["opponent_strategy"],
                    payoff=float(e["payoff"]),
                )
            )

        # ---- outcomes -------------------------------------------------
        for o in data.get("outcomes", []):
            model.add_outcome(
                GameOutcome(
                    player_names=tuple(o["players"]),
                    strategies=tuple(o["strategies"]),
                    payoffs=tuple(float(x) for x in o["payoffs"]),
                    is_nash=o.get("is_nash", False),
                )
            )

        # ---- composition mode -----------------------------------------
        mode_str = data.get("composition_mode", "independent")
        model.composition_mode = CompositionMode(mode_str)

        return True, model, "OK"

    except (KeyError, ValueError, TypeError) as exc:
        return False, None, f"Model construction error: {exc}"


# ---------------------------------------------------------------------------
# Constraint factories
# ---------------------------------------------------------------------------

def station_validity() -> Constraint:
    """All players must have valid NefsMakam stations."""

    def check(output: str) -> tuple[bool, float, str]:
        ok, model, msg = _parse_game(output)
        if not ok:
            return False, 0.0, f"station_validity: {msg}"
        passed, detail = check_station_validity(model)
        score = clamp_score(0.95) if passed else 0.0
        return passed, score, f"station_validity: {detail}"

    return Constraint(name="station_validity", check_fn=check)


def payoff_transformation() -> Constraint:
    """AX62/N-5: Payoffs must transform with station ascent."""

    def check(output: str) -> tuple[bool, float, str]:
        ok, model, msg = _parse_game(output)
        if not ok:
            return False, 0.0, f"payoff_transformation: {msg}"
        passed, detail = check_payoff_transformation(model)
        if passed:
            # Bonus for high ascent ratio
            ratio = 0.0
            for player in model.players:
                r = model.station_ascent_ratio(player.name)
                ratio = max(ratio, r)
            score = clamp_score(0.7 + 0.25 * ratio)
        else:
            score = 0.1
        return passed, score, f"payoff_transformation: {detail}"

    return Constraint(name="payoff_transformation", check_fn=check)


def strategy_completeness() -> Constraint:
    """Every player must have at least one strategy."""

    def check(output: str) -> tuple[bool, float, str]:
        ok, model, msg = _parse_game(output)
        if not ok:
            return False, 0.0, f"strategy_completeness: {msg}"
        passed, detail = check_strategy_completeness(model)
        score = clamp_score(0.95) if passed else 0.0
        return passed, score, f"strategy_completeness: {detail}"

    return Constraint(name="strategy_completeness", check_fn=check)


def nash_consistency() -> Constraint:
    """Nash equilibria must be internally consistent."""

    def check(output: str) -> tuple[bool, float, str]:
        ok, model, msg = _parse_game(output)
        if not ok:
            return False, 0.0, f"nash_consistency: {msg}"
        passed, detail = check_nash_consistency(model)
        score = clamp_score(0.95) if passed else 0.1
        return passed, score, f"nash_consistency: {detail}"

    return Constraint(name="nash_consistency", check_fn=check)


def game_convergence_bound() -> Constraint:
    """KV₄/T6: No convergence score may reach 1.0."""

    def check(output: str) -> tuple[bool, float, str]:
        ok, model, msg = _parse_game(output)
        if not ok:
            return False, 0.0, f"game_convergence_bound: {msg}"
        passed, detail = check_convergence_bound(model)
        score = clamp_score(0.95) if passed else 0.0
        return passed, score, f"game_convergence_bound: {detail}"

    return Constraint(name="game_convergence_bound", check_fn=check)


def tesanud_infirad_asymmetry() -> Constraint:
    """AX63: Composition mode must be correctly classified."""

    def check(output: str) -> tuple[bool, float, str]:
        ok, model, msg = _parse_game(output)
        if not ok:
            return False, 0.0, f"tesanud_infirad: {msg}"
        passed, detail = check_tesanud_infirad(model)
        score = clamp_score(0.95) if passed else 0.1
        return passed, score, f"tesanud_infirad: {detail}"

    return Constraint(name="tesanud_infirad_asymmetry", check_fn=check)


def game_independence() -> Constraint:
    """KV₇: Players must have independently defined game elements."""

    def check(output: str) -> tuple[bool, float, str]:
        ok, model, msg = _parse_game(output)
        if not ok:
            return False, 0.0, f"game_independence: {msg}"
        passed, detail = check_independence(model)
        score = clamp_score(0.95) if passed else 0.1
        return passed, score, f"game_independence: {detail}"

    return Constraint(name="game_independence", check_fn=check)


def game_transparency() -> Constraint:
    """AX57: Model state must be fully inspectable."""

    def check(output: str) -> tuple[bool, float, str]:
        ok, model, msg = _parse_game(output)
        if not ok:
            return False, 0.0, f"game_transparency: {msg}"
        passed, detail = check_transparency(model)
        score = clamp_score(0.95) if passed else 0.0
        return passed, score, f"game_transparency: {detail}"

    return Constraint(name="game_transparency", check_fn=check)


def game_convergence_score() -> Constraint:
    """KV₄/T6: Overall convergence score in [0, 1) — flag if ≥ 1.0."""

    def check(output: str) -> tuple[bool, float, str]:
        ok, model, msg = _parse_game(output)
        if not ok:
            return False, 0.0, f"game_convergence: {msg}"
        score = yakinlasma(model)
        if score >= 1.0:
            return False, score, (
                f"KV4/T6 VIOLATION: yakinlasma = {score}"
            )
        return True, score, f"game_convergence: yakinlasma = {score:.4f}"

    return Constraint(name="game_convergence_score", check_fn=check)


def valid_game_entry() -> Constraint:
    """Composite constraint — all individual checks must pass.

    Runs all 8 sub-constraints and reports a combined result.
    """
    sub_constraints = [
        station_validity(),
        payoff_transformation(),
        strategy_completeness(),
        nash_consistency(),
        game_convergence_bound(),
        tesanud_infirad_asymmetry(),
        game_independence(),
        game_transparency(),
    ]

    def check(output: str) -> tuple[bool, float, str]:
        results = [c.check(output) for c in sub_constraints]
        all_passed = all(r.passed for r in results)

        if all_passed:
            score = clamp_score(0.95)
            return True, score, "valid_game_entry: All checks passed"

        failures = [r.message for r in results if not r.passed]
        avg = sum(r.score for r in results) / len(results) if results else 0.0
        score = clamp_score(avg)
        return False, score, f"valid_game_entry: {'; '.join(failures)}"

    return Constraint(name="valid_game_entry", check_fn=check)
