"""Tests for readable objects."""

from unittest.mock import AsyncMock, MagicMock

import pytest

from mudlib.player import Player, players
from mudlib.thing import Thing
from mudlib.zone import Zone


@pytest.fixture(autouse=True)
def clear_state():
    players.clear()
    yield
    players.clear()


@pytest.fixture
def zone():
    terrain = [["." for _ in range(10)] for _ in range(10)]
    return Zone(
        name="library",
        width=10,
        height=10,
        terrain=terrain,
    )


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


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


# --- Thing model ---


def test_thing_readable_text_default():
    """Thing has empty readable_text by default."""
    t = Thing(name="rock")
    assert t.readable_text == ""


def test_thing_readable_text_set():
    """Thing can have readable_text."""
    t = Thing(name="sign", readable_text="welcome to town")
    assert t.readable_text == "welcome to town"


# --- Template loading ---


def test_thing_template_readable_text():
    """ThingTemplate parses readable_text from TOML data."""
    import pathlib
    import tempfile

    from mudlib.things import load_thing_template

    with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f:
        f.write("""
name = "old sign"
description = "a weathered wooden sign"
portable = false
readable_text = "beware of the goblin king"
""")
        temp_path = pathlib.Path(f.name)

    try:
        template = load_thing_template(temp_path)
        assert template.readable_text == "beware of the goblin king"
    finally:
        temp_path.unlink()


def test_thing_template_readable_text_default():
    """ThingTemplate defaults readable_text to empty string."""
    import pathlib
    import tempfile

    from mudlib.things import load_thing_template

    with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f:
        f.write("""
name = "rock"
description = "a plain rock"
""")
        temp_path = pathlib.Path(f.name)

    try:
        template = load_thing_template(temp_path)
        assert template.readable_text == ""
    finally:
        temp_path.unlink()


def test_spawn_thing_readable_text():
    """spawn_thing passes readable_text to Thing instance."""
    from mudlib.things import ThingTemplate, spawn_thing

    template = ThingTemplate(
        name="notice",
        description="a posted notice",
        readable_text="town meeting at noon",
    )
    thing = spawn_thing(template, location=None)
    assert thing.readable_text == "town meeting at noon"


# --- Read command ---


@pytest.mark.asyncio
async def test_read_thing_on_ground(zone, mock_writer, mock_reader):
    """Reading a thing on the ground shows its text."""
    from mudlib.commands.read import cmd_read

    player = Player(
        name="reader",
        x=5,
        y=5,
        writer=mock_writer,
        reader=mock_reader,
        location=zone,
    )
    zone._contents.append(player)
    players["reader"] = player

    Thing(
        name="sign",
        description="a wooden sign",
        readable_text="welcome to the forest",
        location=zone,
        x=5,
        y=5,
    )

    await cmd_read(player, "sign")

    mock_writer.write.assert_called()
    written = mock_writer.write.call_args_list[-1][0][0]
    assert "welcome to the forest" in written


@pytest.mark.asyncio
async def test_read_thing_in_inventory(zone, mock_writer, mock_reader):
    """Reading a thing in inventory shows its text."""
    from mudlib.commands.read import cmd_read

    player = Player(
        name="reader",
        x=5,
        y=5,
        writer=mock_writer,
        reader=mock_reader,
        location=zone,
    )
    zone._contents.append(player)
    players["reader"] = player

    Thing(
        name="scroll",
        description="an ancient scroll",
        readable_text="the ancient prophecy speaks of...",
        location=player,
    )

    await cmd_read(player, "scroll")

    mock_writer.write.assert_called()
    written = mock_writer.write.call_args_list[-1][0][0]
    assert "the ancient prophecy speaks of" in written


@pytest.mark.asyncio
async def test_read_no_target(zone, mock_writer, mock_reader):
    """Reading without a target shows error."""
    from mudlib.commands.read import cmd_read

    player = Player(
        name="reader",
        x=5,
        y=5,
        writer=mock_writer,
        reader=mock_reader,
        location=zone,
    )
    zone._contents.append(player)

    await cmd_read(player, "")

    mock_writer.write.assert_called()
    written = mock_writer.write.call_args_list[0][0][0]
    assert "read what" in written.lower()


@pytest.mark.asyncio
async def test_read_not_found(zone, mock_writer, mock_reader):
    """Reading something not present shows error."""
    from mudlib.commands.read import cmd_read

    player = Player(
        name="reader",
        x=5,
        y=5,
        writer=mock_writer,
        reader=mock_reader,
        location=zone,
    )
    zone._contents.append(player)

    await cmd_read(player, "scroll")

    mock_writer.write.assert_called()
    written = mock_writer.write.call_args_list[0][0][0]
    assert "don't see" in written.lower() or "nothing" in written.lower()


@pytest.mark.asyncio
async def test_read_not_readable(zone, mock_writer, mock_reader):
    """Reading a thing with no readable_text shows error."""
    from mudlib.commands.read import cmd_read

    player = Player(
        name="reader",
        x=5,
        y=5,
        writer=mock_writer,
        reader=mock_reader,
        location=zone,
    )
    zone._contents.append(player)

    Thing(
        name="rock",
        description="a plain rock",
        location=zone,
        x=5,
        y=5,
    )

    await cmd_read(player, "rock")

    mock_writer.write.assert_called()
    written = mock_writer.write.call_args_list[0][0][0]
    assert "nothing to read" in written.lower()


@pytest.mark.asyncio
async def test_read_by_alias(zone, mock_writer, mock_reader):
    """Can read a thing by its alias."""
    from mudlib.commands.read import cmd_read

    player = Player(
        name="reader",
        x=5,
        y=5,
        writer=mock_writer,
        reader=mock_reader,
        location=zone,
    )
    zone._contents.append(player)

    Thing(
        name="leather-bound book",
        aliases=["book", "tome"],
        description="a leather-bound book",
        readable_text="once upon a time...",
        location=zone,
        x=5,
        y=5,
    )

    await cmd_read(player, "tome")

    mock_writer.write.assert_called()
    written = mock_writer.write.call_args_list[-1][0][0]
    assert "once upon a time" in written
