"""Tests for crafting commands."""

from unittest.mock import AsyncMock, MagicMock

import pytest

from mudlib.commands.crafting import cmd_craft, cmd_recipes
from mudlib.crafting import Recipe, recipes
from mudlib.player import Player
from mudlib.things import ThingTemplate, spawn_thing, thing_templates
from mudlib.zone import Zone
from mudlib.zones import register_zone, zone_registry


@pytest.fixture(autouse=True)
def _clean_registries():
    """Snapshot and restore registries to prevent test leakage."""
    saved_zones = dict(zone_registry)
    saved_templates = dict(thing_templates)
    saved_recipes = dict(recipes)
    zone_registry.clear()
    thing_templates.clear()
    recipes.clear()
    yield
    zone_registry.clear()
    zone_registry.update(saved_zones)
    thing_templates.clear()
    thing_templates.update(saved_templates)
    recipes.clear()
    recipes.update(saved_recipes)


def _make_zone(name="overworld", width=20, height=20):
    """Create a test zone."""
    terrain = [["." for _ in range(width)] for _ in range(height)]
    zone = Zone(
        name=name,
        description=name,
        width=width,
        height=height,
        terrain=terrain,
        toroidal=True,
    )
    register_zone(name, zone)
    return zone


def _make_player(name="tester", zone=None, x=5, y=5):
    """Create a test player with mock writer."""
    mock_writer = MagicMock()
    mock_writer.write = MagicMock()
    mock_writer.drain = AsyncMock()
    return Player(name=name, location=zone, x=x, y=y, writer=mock_writer)


@pytest.mark.asyncio
async def test_craft_success():
    """Craft with ingredients: consumed, result in inventory."""
    zone = _make_zone()
    player = _make_player(zone=zone)

    # Register templates
    plank_template = ThingTemplate(name="plank", description="A wooden plank")
    nail_template = ThingTemplate(name="nail", description="A small nail")
    table_template = ThingTemplate(name="table", description="A sturdy table")
    thing_templates["plank"] = plank_template
    thing_templates["nail"] = nail_template
    thing_templates["table"] = table_template

    # Add ingredients to player inventory
    plank1 = spawn_thing(plank_template, player)
    plank2 = spawn_thing(plank_template, player)
    nail1 = spawn_thing(nail_template, player)

    # Register recipe
    recipe = Recipe(
        name="wooden_table",
        description="Craft a table",
        ingredients=["plank", "plank", "nail"],
        result="table",
    )
    recipes["wooden_table"] = recipe

    # Craft the item
    await cmd_craft(player, "wooden_table")

    # Check success message was sent
    assert player.writer.write.called
    output = "".join(call.args[0] for call in player.writer.write.call_args_list)
    assert "table" in output.lower()

    # Check ingredients were consumed
    inventory = player.contents
    assert plank1 not in inventory
    assert plank2 not in inventory
    assert nail1 not in inventory

    # Check result was added to inventory
    table_in_inventory = [obj for obj in inventory if obj.name == "table"]
    assert len(table_in_inventory) == 1


@pytest.mark.asyncio
async def test_craft_missing_ingredients():
    """Error message listing what's missing."""
    zone = _make_zone()
    player = _make_player(zone=zone)

    # Register templates
    plank_template = ThingTemplate(name="plank", description="A wooden plank")
    table_template = ThingTemplate(name="table", description="A sturdy table")
    thing_templates["plank"] = plank_template
    thing_templates["table"] = table_template

    # Add only one plank (recipe needs two)
    spawn_thing(plank_template, player)

    # Register recipe needing two planks
    recipe = Recipe(
        name="wooden_table",
        description="Craft a table",
        ingredients=["plank", "plank"],
        result="table",
    )
    recipes["wooden_table"] = recipe

    # Try to craft
    await cmd_craft(player, "wooden_table")

    # Check error message
    assert player.writer.write.called
    output = "".join(call.args[0] for call in player.writer.write.call_args_list)
    assert "missing" in output.lower() or "need" in output.lower()


@pytest.mark.asyncio
async def test_craft_unknown_recipe():
    """Error for nonexistent recipe."""
    zone = _make_zone()
    player = _make_player(zone=zone)

    await cmd_craft(player, "nonexistent_recipe")

    assert player.writer.write.called
    output = "".join(call.args[0] for call in player.writer.write.call_args_list)
    assert "unknown" in output.lower() or "not found" in output.lower()


@pytest.mark.asyncio
async def test_craft_no_args():
    """Error with usage when no args provided."""
    zone = _make_zone()
    player = _make_player(zone=zone)

    await cmd_craft(player, "")

    assert player.writer.write.called
    output = "".join(call.args[0] for call in player.writer.write.call_args_list)
    assert "usage" in output.lower() or "craft" in output.lower()


@pytest.mark.asyncio
async def test_craft_unknown_result_template():
    """Recipe result template not in thing_templates."""
    zone = _make_zone()
    player = _make_player(zone=zone)

    # Register template for ingredient
    plank_template = ThingTemplate(name="plank", description="A wooden plank")
    thing_templates["plank"] = plank_template

    # Add ingredient to inventory
    spawn_thing(plank_template, player)

    # Register recipe with unknown result template
    recipe = Recipe(
        name="broken_recipe",
        description="This recipe has a broken result",
        ingredients=["plank"],
        result="unknown_item",  # This template doesn't exist
    )
    recipes["broken_recipe"] = recipe

    # Try to craft
    await cmd_craft(player, "broken_recipe")

    # Check error message
    assert player.writer.write.called
    output = "".join(call.args[0] for call in player.writer.write.call_args_list)
    assert "error" in output.lower() or "unknown" in output.lower()


@pytest.mark.asyncio
async def test_recipes_list():
    """Shows available recipes."""
    zone = _make_zone()
    player = _make_player(zone=zone)

    # Register some recipes
    recipes["wooden_table"] = Recipe(
        name="wooden_table",
        description="Craft a table",
        ingredients=["plank", "plank"],
        result="table",
    )
    recipes["wooden_chair"] = Recipe(
        name="wooden_chair",
        description="Craft a chair",
        ingredients=["plank"],
        result="chair",
    )

    await cmd_recipes(player, "")

    assert player.writer.write.called
    output = "".join(call.args[0] for call in player.writer.write.call_args_list)
    assert "wooden_table" in output.lower()
    assert "wooden_chair" in output.lower()


@pytest.mark.asyncio
async def test_recipes_detail():
    """Shows specific recipe details."""
    zone = _make_zone()
    player = _make_player(zone=zone)

    # Register a recipe
    recipes["wooden_table"] = Recipe(
        name="wooden_table",
        description="Craft a sturdy table",
        ingredients=["plank", "plank", "nail", "nail"],
        result="table",
    )

    await cmd_recipes(player, "wooden_table")

    assert player.writer.write.called
    output = "".join(call.args[0] for call in player.writer.write.call_args_list)
    assert "wooden_table" in output.lower()
    assert "plank" in output.lower()
    assert "nail" in output.lower()
    assert "table" in output.lower()
