from __future__ import annotations

from collections import Counter

import numpy as np
from coolrl_lost_cities.games.classic.game import GameState, classic_config

from coolrl_lost_cities.games.classic.jax_ppo_policy import (
    JaxPPOPolicy,
    snapshot_to_jax_state,
)
from coolrl_lost_cities.games.classic.snapshots import snapshot_from_state
from lost_cities_jax.engine import board_score, current_hand_sorted, legal_action_mask
from lost_cities_jax.human_play import PolicyEval


def test_snapshot_to_jax_state_preserves_initial_public_state() -> None:
    state = GameState.new_game(classic_config(), seed=7)
    snapshot = snapshot_from_state(state)

    converted, hand_slot_map = snapshot_to_jax_state(snapshot)

    assert int(converted.to_move) == snapshot.current_player
    assert int(converted.draw_ptr) == 16
    assert hand_slot_map == sorted(
        hand_slot_map,
        key=lambda slot: (
            snapshot.hands[0][slot].color,
            snapshot.hands[0][slot].rank,
        ),
    )
    assert np.asarray(current_hand_sorted(converted)).shape == (8,)
    assert np.asarray(legal_action_mask(converted)).any()
    assert np.asarray(board_score(converted)).tolist() == [0.0, 0.0]


def test_jax_policy_splits_atomic_action_across_classic_phases(monkeypatch) -> None:
    state = GameState.new_game(classic_config(), seed=11)
    converted, hand_slot_map = snapshot_to_jax_state(snapshot_from_state(state))
    legal = np.flatnonzero(np.asarray(legal_action_mask(converted), dtype=bool))
    atomic_action = int(legal[0])
    expected_slot = hand_slot_map[atomic_action // 12]
    expected_place = (atomic_action % 12) // 6
    expected_draw = atomic_action % 6

    monkeypatch.setattr(
        "coolrl_lost_cities.games.classic.jax_ppo_policy.evaluate_agent_policy",
        lambda *_args: PolicyEval(action=atomic_action, top3=[], value=0.0),
    )
    policy = object.__new__(JaxPPOPolicy)
    policy.cfg = object()
    policy.params = object()
    policy.model = object()
    policy.pending_draw = None
    policy.last_evaluation = None

    card_action = policy.act(state)
    assert card_action == 2 * expected_slot + expected_place
    state.apply_action(card_action)
    assert state.phase == "draw"
    assert policy.act(state) == expected_draw
    assert policy.pending_draw is None


def test_snapshot_conversion_marks_known_discard_draw_in_public_hand() -> None:
    state = GameState.new_game(classic_config(), seed=19)
    snapshot = snapshot_from_state(state)
    card = snapshot.hands[1][0]
    face = (card.color, 0 if card.is_handshake else card.numeric_value(2))

    converted, _ = snapshot_to_jax_state(
        snapshot,
        [Counter(), Counter({face: 1})],
    )

    public = np.asarray(converted.hand_public, dtype=bool)
    locations = np.asarray(converted.card_loc)
    assert public.sum() == 1
    assert np.all(locations[public] == 2)
