"""Tests for editor integration with the shell and command system."""

from unittest.mock import AsyncMock, MagicMock

import pytest

from mudlib import commands
from mudlib.commands.edit import cmd_edit
from mudlib.editor import Editor
from mudlib.player import Player


@pytest.fixture
def mock_writer():
    writer = MagicMock()
    writer.write = MagicMock()
    writer.drain = AsyncMock()
    return writer


@pytest.fixture
def mock_reader():
    return MagicMock()


@pytest.fixture
def player(mock_reader, mock_writer):
    return Player(name="TestPlayer", x=5, y=5, reader=mock_reader, writer=mock_writer)


@pytest.mark.asyncio
async def test_edit_command_enters_editor_mode(player):
    """Test that edit command sets up editor mode."""
    assert player.mode == "normal"
    assert player.editor is None

    await cmd_edit(player, "")

    assert player.mode == "editor"
    assert player.editor is not None
    assert isinstance(player.editor, Editor)
    assert player.mode_stack == ["normal", "editor"]


@pytest.mark.asyncio
async def test_edit_command_sends_welcome_message(player, mock_writer):
    """Test that edit command sends welcome message."""
    await cmd_edit(player, "")

    assert mock_writer.write.called
    output = "".join([call[0][0] for call in mock_writer.write.call_args_list])
    assert "editor" in output.lower()
    assert ":h" in output


@pytest.mark.asyncio
async def test_edit_command_sets_save_callback(player):
    """Test that edit command sets up a save callback."""
    await cmd_edit(player, "")

    assert player.editor is not None
    assert player.editor.save_callback is not None


@pytest.mark.asyncio
async def test_editor_handle_input_preserves_whitespace(player):
    """Test that editor receives input with whitespace preserved."""
    await cmd_edit(player, "")

    # Simulate code with indentation
    response = await player.editor.handle_input("    def foo():")

    assert player.editor.buffer == ["    def foo():"]
    assert response.done is False


@pytest.mark.asyncio
async def test_editor_done_clears_editor_and_pops_mode(player):
    """Test that when editor returns done=True, mode is popped and editor cleared."""
    await cmd_edit(player, "")

    assert player.mode == "editor"
    assert player.editor is not None

    # Quit the editor
    response = await player.editor.handle_input(":q")
    assert response.done is True

    # Simulate what the shell loop should do
    player.editor = None
    player.mode_stack.pop()

    assert player.mode == "normal"
    assert player.editor is None


@pytest.mark.asyncio
async def test_editor_save_callback_sends_message(player, mock_writer):
    """Test that save callback sends confirmation to player."""
    await cmd_edit(player, "")
    mock_writer.reset_mock()

    await player.editor.handle_input("test line")
    response = await player.editor.handle_input(":w")

    assert response.saved is True
    # Save callback should have sent a message
    assert mock_writer.write.called
    output = "".join([call[0][0] for call in mock_writer.write.call_args_list])
    assert "saved" in output.lower()


@pytest.mark.asyncio
async def test_edit_command_only_works_in_normal_mode(player):
    """Test that edit command has mode='normal' restriction."""
    # Push a different mode onto the stack
    player.mode_stack.append("combat")

    # Try to invoke edit command through dispatch
    await commands.dispatch(player, "edit")

    # Should be blocked by mode check
    assert player.mode == "combat"
    assert player.editor is None


@pytest.mark.asyncio
async def test_mode_stack_push_and_pop(player):
    """Test mode stack mechanics for editor mode."""
    assert player.mode_stack == ["normal"]
    assert player.mode == "normal"

    # Enter editor mode
    player.mode_stack.append("editor")
    assert player.mode == "editor"
    assert player.mode_stack == ["normal", "editor"]

    # Exit editor mode
    player.mode_stack.pop()
    assert player.mode == "normal"
    assert player.mode_stack == ["normal"]


@pytest.mark.asyncio
async def test_empty_input_allowed_in_editor(player):
    """Test that empty lines are valid in editor mode (for blank lines in code)."""
    await cmd_edit(player, "")

    # Empty line should be appended to buffer
    response = await player.editor.handle_input("")
    assert response.done is False
    assert player.editor.buffer == [""]


@pytest.mark.asyncio
async def test_editor_prompt_uses_cursor(player):
    """Test that editor prompt should show line number from cursor."""
    await cmd_edit(player, "")

    # Add some lines
    await player.editor.handle_input("line 1")
    await player.editor.handle_input("line 2")

    # After appending, cursor should be at the last line (1 in 0-indexed)
    # This test verifies the cursor field exists and can be used for prompts
    assert player.editor.cursor == 1
    # Shell loop prompt: f"  {player.editor.cursor + 1}> " = "  2> "


@pytest.mark.asyncio
async def test_edit_no_args_opens_blank_editor(player):
    """Test that edit with no args opens a blank editor."""
    await cmd_edit(player, "")

    assert player.editor is not None
    assert player.editor.buffer == []
    assert player.mode == "editor"


@pytest.mark.asyncio
async def test_edit_combat_move_opens_toml(player, tmp_path):
    """Test that edit roundhouse opens the TOML file for editing."""
    from mudlib.combat import commands as combat_commands

    # Create a test TOML file
    toml_content = """name = "roundhouse"
aliases = ["rh"]
move_type = "attack"
stamina_cost = 8.0
timing_window_ms = 2000
"""
    toml_file = tmp_path / "roundhouse.toml"
    toml_file.write_text(toml_content)

    # Set up combat moves
    combat_commands.combat_moves = {"roundhouse": MagicMock(command="roundhouse")}
    combat_commands.combat_content_dir = tmp_path

    await cmd_edit(player, "roundhouse")

    assert player.editor is not None
    assert player.mode == "editor"
    assert toml_content in "\n".join(player.editor.buffer)


@pytest.mark.asyncio
async def test_edit_combat_move_saves_to_disk(player, tmp_path, mock_writer):
    """Test that saving in editor writes back to the TOML file."""
    from mudlib.combat import commands as combat_commands

    # Create a test TOML file
    original_content = """name = "roundhouse"
aliases = ["rh"]
move_type = "attack"
"""
    toml_file = tmp_path / "roundhouse.toml"
    toml_file.write_text(original_content)

    # Set up combat moves
    combat_commands.combat_moves = {"roundhouse": MagicMock(command="roundhouse")}
    combat_commands.combat_content_dir = tmp_path

    await cmd_edit(player, "roundhouse")
    mock_writer.reset_mock()

    # Modify the buffer
    player.editor.buffer = [
        'name = "roundhouse"',
        'aliases = ["rh"]',
        'move_type = "attack"',
        "stamina_cost = 9.0",
    ]

    # Save
    await player.editor.handle_input(":w")

    # Check that file was written
    saved_content = toml_file.read_text()
    assert "stamina_cost = 9.0" in saved_content
    assert mock_writer.write.called


@pytest.mark.asyncio
async def test_edit_variant_base_opens_toml(player, tmp_path):
    """Test that edit punch opens punch.toml (variant base name)."""
    from mudlib.combat import commands as combat_commands

    # Create punch.toml with variants
    toml_content = """name = "punch"
move_type = "attack"

[variants.left]
aliases = ["pl"]
"""
    toml_file = tmp_path / "punch.toml"
    toml_file.write_text(toml_content)

    # Set up combat moves with variant
    combat_commands.combat_moves = {
        "punch left": MagicMock(command="punch", variant="left")
    }
    combat_commands.combat_content_dir = tmp_path

    await cmd_edit(player, "punch")

    assert player.editor is not None
    assert player.mode == "editor"
    assert "[variants.left]" in "\n".join(player.editor.buffer)


@pytest.mark.asyncio
async def test_edit_unknown_content_shows_error(player, mock_writer, tmp_path):
    """Test that edit nonexistent shows an error."""
    from mudlib.combat import commands as combat_commands

    combat_commands.combat_moves = {}
    combat_commands.combat_content_dir = tmp_path

    await cmd_edit(player, "nonexistent")

    assert player.editor is None
    assert player.mode == "normal"
    assert mock_writer.write.called
    output = "".join([call[0][0] for call in mock_writer.write.call_args_list])
    assert "unknown" in output.lower()
    assert "nonexistent" in output.lower()


@pytest.mark.asyncio
async def test_edit_combat_move_uses_toml_content_type(player, tmp_path):
    """Test that editor for combat moves uses toml content type."""
    from mudlib.combat import commands as combat_commands

    toml_file = tmp_path / "roundhouse.toml"
    toml_file.write_text("name = 'roundhouse'\n")

    combat_commands.combat_moves = {"roundhouse": MagicMock(command="roundhouse")}
    combat_commands.combat_content_dir = tmp_path

    await cmd_edit(player, "roundhouse")

    assert player.editor is not None
    assert player.editor.content_type == "toml"
