from __future__ import annotations

import os
import tempfile

from blokus_ui.session import GameSession
from blokus_ui.store import RunStore


def make_session(**kwargs):
    """Create a GameSession with sensible defaults for testing."""
    defaults = {
        "board_size": 7,
        "num_players": 2,
        "human_player": 0,
        "bot_type": "random",
        "bot_seed": 42,
    }
    defaults.update(kwargs)
    return GameSession(**defaults)


class TestGameSession:
    def test_creation(self):
        session = make_session()
        assert session.board_size == 7
        assert session.num_players == 2
        assert session.human_player == 0
        assert session.bot_type_name == "random"

    def test_initial_state(self):
        session = make_session()
        state = session.get_state()
        assert state["board_size"] == 7
        assert state["human_player"] == 0
        assert state["game_over"] is False
        assert len(state["players"]) == 2
        assert len(state["available_pieces"]) > 0

    def test_valid_placements(self):
        session = make_session()
        placements = session.get_valid_placements(0)
        assert len(placements) > 0
        for p in placements:
            assert "action" in p
            assert "cells" in p
            assert "x" in p
            assert "y" in p

    def test_apply_human_action(self):
        session = make_session()
        placements = session.get_valid_placements(0)
        action = placements[0]["action"]
        result = session.apply_human_action(action)
        assert result["success"] is True

    def test_invalid_action(self):
        session = make_session()
        result = session.apply_human_action(999999)
        assert result["success"] is False

    def test_move_history(self):
        session = make_session()
        placements = session.get_valid_placements(0)
        action = placements[0]["action"]
        session.apply_human_action(action)
        assert len(session.move_history) > 0
        move = session.move_history[0]
        assert move["player_idx"] == 0
        assert "piece_name" in move
        assert "x" in move
        assert "y" in move

    def test_game_over_detection(self):
        session = make_session(board_size=7)
        steps = 0
        while not session.game_over and steps < 200:
            state = session.get_state()
            moved = False
            for piece in state["available_pieces"]:
                placements = session.get_valid_placements(piece["id"])
                if placements:
                    session.apply_human_action(placements[0]["action"])
                    moved = True
                    break
            if not moved:
                break
            steps += 1
        assert session.game_over is True

    def test_serialization(self):
        session = make_session()
        placements = session.get_valid_placements(0)
        session.apply_human_action(placements[0]["action"])

        data = session.to_dict()
        assert "config" in data
        assert "move_history" in data
        assert "game_over" in data
        assert data["config"]["board_size"] == 7
        assert data["config"]["num_players"] == 2

    def test_deserialization(self):
        session = make_session()
        placements = session.get_valid_placements(0)
        session.apply_human_action(placements[0]["action"])

        data = session.to_dict()
        restored = GameSession.from_dict(data)

        assert restored.board_size == session.board_size
        assert restored.num_players == session.num_players
        assert len(restored.move_history) == len(session.move_history)
        assert restored.game_over == session.game_over

    def test_statistics(self):
        session = make_session()
        placements = session.get_valid_placements(0)
        session.apply_human_action(placements[0]["action"])

        stats = session.get_statistics()
        assert "coverage" in stats
        assert "pieces_placed" in stats
        assert "squares_placed" in stats
        assert "total_moves" in stats
        assert len(stats["pieces_placed"]) == 2
        assert len(stats["squares_placed"]) == 2

    def test_four_player_game(self):
        session = make_session(num_players=4)
        state = session.get_state()
        assert len(state["players"]) == 4

    def test_greedy_bot(self):
        session = make_session(bot_type="greedy")
        placements = session.get_valid_placements(0)
        assert len(placements) > 0

    def test_minimax_bot(self):
        session = make_session(bot_type="minimax")
        placements = session.get_valid_placements(0)
        assert len(placements) > 0


class TestRunStore:
    def test_save_and_load(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            store = RunStore(runs_dir=tmpdir)
            session = make_session()
            placements = session.get_valid_placements(0)
            session.apply_human_action(placements[0]["action"])

            run_id = store.save(session, name="test_run")
            assert run_id is not None

            run_data = store.load(run_id)
            assert run_data is not None
            assert run_data["name"] == "test_run"
            assert run_data["data"]["config"]["board_size"] == 7

    def test_list_runs(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            store = RunStore(runs_dir=tmpdir)
            session = make_session()
            store.save(session, name="run1")

            session2 = make_session(bot_type="greedy", bot_seed=43)
            store.save(session2, name="run2")

            runs = store.list_runs()
            assert len(runs) == 2
            assert runs[0]["name"] in ("run1", "run2")
            assert runs[0]["board_size"] == 7

    def test_delete_run(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            store = RunStore(runs_dir=tmpdir)
            session = make_session()
            run_id = store.save(session, name="test_run")

            assert store.delete(run_id) is True
            assert store.load(run_id) is None
            assert store.delete(run_id) is False

    def test_load_nonexistent(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            store = RunStore(runs_dir=tmpdir)
            assert store.load("nonexistent") is None

    def test_list_empty(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            store = RunStore(runs_dir=tmpdir)
            assert store.list_runs() == []

    def test_runs_dir_creation(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            runs_dir = os.path.join(tmpdir, "new_dir")
            RunStore(runs_dir=runs_dir)
            assert os.path.exists(runs_dir)
