"""Regression tests for z-machine game compatibility.

Smoke tests a suite of games to ensure the interpreter can load them,
execute basic commands, and reach the input prompt without crashing.
"""

from pathlib import Path

import pytest

from mudlib.zmachine import ZMachine, zscreen, zstream, zui
from mudlib.zmachine.trivialzui import (
    TrivialAudio,
    TrivialFilesystem,
    TrivialScreen,
)
from mudlib.zmachine.zcpu import ZCpuQuit, ZCpuRestart

STORIES_DIR = Path(__file__).parent.parent / "content" / "stories"

# Game test suite: (filename, commands to feed)
GAMES = [
    ("zork1.z3", ["look", "open mailbox", "read leaflet"]),
    ("curses.z5", ["look", "inventory", "north"]),
    ("photopia.z5", ["look", "yes", "look"]),
    ("Tangle.z5", ["look", "inventory", "north"]),
    ("shade.z5", ["look", "inventory", "look"]),
    ("LostPig.z8", ["look", "inventory", "north"]),
    ("anchor.z8", ["look", "inventory", "north"]),
]


class QuietScreen(TrivialScreen):
    """Screen for testing that never shows [MORE] prompts."""

    def __init__(self):
        super().__init__()
        # Set infinite rows to prevent [MORE] prompts
        self._rows = zscreen.INFINITE_ROWS


class AutoInputStream(zstream.ZInputStream):
    """Input stream that auto-feeds commands."""

    def __init__(self, commands=None):
        super().__init__()
        self._commands = commands or []
        self._input_count = 0

    def read_line(self, *args, **kwargs):
        if self._input_count >= len(self._commands):
            raise ZCpuQuit
        cmd = self._commands[self._input_count]
        self._input_count += 1
        return cmd

    def read_char(self, *args, **kwargs):
        if self._input_count >= len(self._commands):
            raise ZCpuQuit
        cmd = self._commands[self._input_count]
        self._input_count += 1
        # Return first character as ord
        return ord(cmd[0]) if cmd else ord(" ")


@pytest.mark.parametrize("game,commands", GAMES, ids=[g[0] for g in GAMES])
def test_game_smoke(game, commands):
    """Run game through interpreter, feed commands, verify no crash."""
    story_path = STORIES_DIR / game
    if not story_path.exists():
        pytest.skip(f"{game} not found in {STORIES_DIR}")

    # Load story
    story_bytes = story_path.read_bytes()

    # Create test UI components
    audio = TrivialAudio()
    screen = QuietScreen()
    keyboard = AutoInputStream(commands)
    filesystem = TrivialFilesystem()
    ui = zui.ZUI(audio, screen, keyboard, filesystem)

    # Create interpreter
    zm = ZMachine(story_bytes, ui)

    # Step through instructions
    step_count = 0
    max_steps = 500_000  # Enough to get through several commands

    try:
        while step_count < max_steps:
            (opcode_class, opcode_number, operands) = (
                zm._cpu._opdecoder.get_next_instruction()
            )

            implemented, func = zm._cpu._get_handler(opcode_class, opcode_number)

            # If unimplemented, that's a test failure
            assert implemented, (
                f"Unimplemented opcode {opcode_class}:{opcode_number:02x}"
            )

            # Execute instruction
            func(zm._cpu, *operands)

            step_count += 1

    except ZCpuQuit:
        # Normal exit (ran out of commands)
        pass
    except ZCpuRestart:
        # Some games restart - this is fine for smoke test
        pass

    # Sanity check: at least some instructions executed
    assert step_count >= 100, (
        f"Only {step_count} instructions executed, expected at least 100"
    )
