"""Tests for player alias system."""

import os
import tempfile
from pathlib import Path
from unittest.mock import MagicMock

import pytest

from mudlib.commands import CommandDefinition, dispatch, register
from mudlib.player import Player
from mudlib.store import (
    create_account,
    init_db,
    load_aliases,
    save_aliases,
    save_player,
)


# Persistence tests
def test_save_and_load_aliases_roundtrip():
    """Save and load aliases from database."""
    with tempfile.TemporaryDirectory() as tmpdir:
        db_path = Path(tmpdir) / "test.db"
        init_db(db_path)

        aliases = {"pr": "punch right", "pl": "punch left", "l": "look"}
        save_aliases("goku", aliases, db_path)

        loaded = load_aliases("goku", db_path)
        assert loaded == aliases


def test_load_aliases_empty():
    """Loading aliases for player with none returns empty dict."""
    with tempfile.TemporaryDirectory() as tmpdir:
        db_path = Path(tmpdir) / "test.db"
        init_db(db_path)

        loaded = load_aliases("goku", db_path)
        assert loaded == {}


def test_save_aliases_overwrites_existing():
    """Saving new aliases replaces old ones."""
    with tempfile.TemporaryDirectory() as tmpdir:
        db_path = Path(tmpdir) / "test.db"
        init_db(db_path)

        save_aliases("goku", {"pr": "punch right"}, db_path)
        save_aliases("goku", {"pl": "punch left", "l": "look"}, db_path)

        loaded = load_aliases("goku", db_path)
        assert loaded == {"pl": "punch left", "l": "look"}
        assert "pr" not in loaded


# Alias command tests
@pytest.mark.asyncio
async def test_alias_list_empty(player):
    """alias with no args shows message when no aliases defined."""
    from mudlib.commands.alias import cmd_alias

    await cmd_alias(player, "")
    player.writer.write.assert_called_with("No aliases defined.\r\n")


@pytest.mark.asyncio
async def test_alias_create(player):
    """alias <name> <expansion> creates an alias."""
    from mudlib.commands.alias import cmd_alias

    await cmd_alias(player, "pr punch right")
    assert player.aliases["pr"] == "punch right"
    player.writer.write.assert_called_with("Alias set: pr -> punch right\r\n")


@pytest.mark.asyncio
async def test_alias_list_with_aliases(player):
    """alias with no args lists all aliases."""
    from mudlib.commands.alias import cmd_alias

    player.aliases = {"pr": "punch right", "pl": "punch left", "l": "look"}

    await cmd_alias(player, "")
    output = player.writer.write.call_args[0][0]
    assert "pr -> punch right" in output
    assert "pl -> punch left" in output
    assert "l -> look" in output


@pytest.mark.asyncio
async def test_alias_show_single(player):
    """alias <name> shows that specific alias."""
    from mudlib.commands.alias import cmd_alias

    player.aliases = {"pr": "punch right", "pl": "punch left"}

    await cmd_alias(player, "pr")
    player.writer.write.assert_called_with("pr -> punch right\r\n")


@pytest.mark.asyncio
async def test_unalias_removes_alias(player):
    """unalias <name> removes an alias."""
    from mudlib.commands.alias import cmd_unalias

    player.aliases = {"pr": "punch right"}

    await cmd_unalias(player, "pr")
    assert "pr" not in player.aliases
    player.writer.write.assert_called_with("Alias removed: pr\r\n")


@pytest.mark.asyncio
async def test_unalias_no_such_alias(player):
    """unalias on non-existent alias shows error."""
    from mudlib.commands.alias import cmd_unalias

    await cmd_unalias(player, "pr")
    player.writer.write.assert_called_with("No such alias: pr\r\n")


@pytest.mark.asyncio
async def test_alias_cannot_override_builtin(player):
    """Cannot alias over existing built-in commands."""
    import mudlib.commands.look  # noqa: F401 - needed to register look command
    from mudlib.commands.alias import cmd_alias

    await cmd_alias(player, "look punch right")
    player.writer.write.assert_called_with(
        "Cannot alias over built-in command: look\r\n"
    )
    assert "look" not in player.aliases


# Dispatch integration tests
@pytest.mark.asyncio
async def test_alias_expands_in_dispatch(player):
    """Aliases are expanded before command dispatch."""
    called_with = []

    async def test_handler(p, args):
        called_with.append(args)

    register(CommandDefinition("testcmd", test_handler))
    player.aliases["tc"] = "testcmd"

    await dispatch(player, "tc hello")
    assert called_with == ["hello"]


@pytest.mark.asyncio
async def test_alias_with_extra_args(player):
    """Alias expansion preserves additional arguments."""
    called_with = []

    async def test_handler(p, args):
        called_with.append(args)

    register(CommandDefinition("testcmd", test_handler))
    player.aliases["tc"] = "testcmd arg1"

    await dispatch(player, "tc arg2 arg3")
    # Expansion: "testcmd arg1" + " arg2 arg3" = "testcmd arg1 arg2 arg3"
    assert called_with == ["arg1 arg2 arg3"]


@pytest.mark.asyncio
async def test_nested_alias_max_depth(player):
    """Nested aliases are limited to prevent infinite recursion."""
    called = []

    async def test_handler(p, args):
        called.append(True)

    register(CommandDefinition("final", test_handler))

    # Create a chain: a -> b -> c -> ... -> final
    player.aliases["a"] = "b"
    player.aliases["b"] = "c"
    player.aliases["c"] = "d"
    player.aliases["d"] = "e"
    player.aliases["e"] = "f"
    player.aliases["f"] = "g"
    player.aliases["g"] = "h"
    player.aliases["h"] = "i"
    player.aliases["i"] = "j"
    player.aliases["j"] = "final"

    await dispatch(player, "a")
    assert len(called) == 1  # Should complete successfully

    # Now create an 11-deep chain that exceeds limit
    player.aliases["j"] = "k"
    player.aliases["k"] = "final"

    called.clear()
    await dispatch(player, "a")
    # Should fail and send error message
    assert len(called) == 0
    assert "Too many nested aliases" in player.writer.write.call_args[0][0]


@pytest.mark.asyncio
async def test_unknown_alias_falls_through(player):
    """Unknown aliases fall through to normal command resolution."""
    player.aliases = {}  # No aliases defined

    # This should hit normal "Unknown command" path
    await dispatch(player, "nonexistent")
    assert "Unknown command" in player.writer.write.call_args[0][0]


# Integration test for persistence
def test_aliases_persist_on_save_player():
    """Aliases are saved when save_player is called."""
    with tempfile.NamedTemporaryFile(delete=False, suffix=".db") as f:
        db_path = f.name

    try:
        init_db(db_path)
        create_account("Goku", "password")

        # Create player with aliases
        mock_writer = MagicMock()
        player = Player(
            name="Goku",
            x=10,
            y=20,
            reader=MagicMock(),
            writer=mock_writer,
        )
        player.aliases = {"pr": "punch right", "pl": "punch left", "l": "look"}

        # Save player (should save aliases too)
        save_player(player)

        # Load aliases directly from database
        loaded = load_aliases("Goku")
        assert loaded == {"pr": "punch right", "pl": "punch left", "l": "look"}

    finally:
        os.unlink(db_path)
