"""Tests for the admin system."""

from unittest.mock import AsyncMock, MagicMock

import pytest

from mudlib.commands.build import cmd_demote, cmd_promote
from mudlib.player import Player, players
from mudlib.store import (
    create_account,
    init_db,
    load_player_data,
    set_admin,
)
from mudlib.zone import Zone
from mudlib.zones import register_zone, zone_registry


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


@pytest.fixture
def db(tmp_path):
    init_db(tmp_path / "test.db")


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


def make_player(name, zone, mock_writer, mock_reader, is_admin=False):
    p = Player(
        name=name,
        x=5,
        y=5,
        writer=mock_writer,
        reader=mock_reader,
        location=zone,
        is_admin=is_admin,
    )
    zone._contents.append(p)
    players[name] = p
    return p


# Store layer tests


@pytest.mark.asyncio
async def test_first_account_is_admin(db):
    """First account created becomes admin."""
    create_account("first", "password")
    data = load_player_data("first")
    assert data is not None
    assert data["is_admin"] is True


@pytest.mark.asyncio
async def test_second_account_not_admin(db):
    """Second account created is not admin."""
    create_account("first", "password")
    create_account("second", "password")

    first_data = load_player_data("first")
    second_data = load_player_data("second")

    assert first_data is not None
    assert first_data["is_admin"] is True
    assert second_data is not None
    assert second_data["is_admin"] is False


@pytest.mark.asyncio
async def test_set_admin(db):
    """set_admin grants admin status to non-admin account."""
    create_account("first", "password")
    create_account("second", "password")

    # Second account should not be admin initially
    data = load_player_data("second")
    assert data is not None
    assert data["is_admin"] is False

    # Promote second account
    result = set_admin("second", True)
    assert result is True

    # Verify admin status
    data = load_player_data("second")
    assert data is not None
    assert data["is_admin"] is True


@pytest.mark.asyncio
async def test_set_admin_nonexistent(db):
    """set_admin returns False for nonexistent account."""
    result = set_admin("nobody", True)
    assert result is False


@pytest.mark.asyncio
async def test_migration_promotes_earliest(db):
    """Migration promotes earliest account when is_admin column added."""
    # Create two accounts
    create_account("alice", "password")
    create_account("bob", "password")

    # Verify first account is admin
    alice_data = load_player_data("alice")
    bob_data = load_player_data("bob")

    assert alice_data is not None
    assert alice_data["is_admin"] is True
    assert bob_data is not None
    assert bob_data["is_admin"] is False


# Command tests


@pytest.mark.asyncio
async def test_promote_no_args(zone, mock_writer, mock_reader, db):
    """promote with no args shows usage."""
    player = make_player("builder", zone, mock_writer, mock_reader, is_admin=True)
    create_account("builder", "password")

    await cmd_promote(player, "")

    output = "".join(call.args[0] for call in player.writer.write.call_args_list)
    assert "Usage:" in output


@pytest.mark.asyncio
async def test_promote_nonexistent(zone, mock_writer, mock_reader, db):
    """promote nonexistent account shows error."""
    player = make_player("builder", zone, mock_writer, mock_reader, is_admin=True)
    create_account("builder", "password")

    await cmd_promote(player, "nobody")

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


@pytest.mark.asyncio
async def test_promote_offline_player(zone, mock_writer, mock_reader, db):
    """promote offline player updates database."""
    player = make_player("builder", zone, mock_writer, mock_reader, is_admin=True)
    create_account("builder", "password")
    create_account("target", "password")

    await cmd_promote(player, "target")

    # Verify database updated
    data = load_player_data("target")
    assert data is not None
    assert data["is_admin"] is True

    output = "".join(call.args[0] for call in player.writer.write.call_args_list)
    assert "is now an admin" in output


@pytest.mark.asyncio
async def test_promote_online_player(zone, mock_writer, mock_reader, db):
    """promote online player updates both database and live state."""
    player = make_player("builder", zone, mock_writer, mock_reader, is_admin=True)
    create_account("builder", "password")
    create_account("target", "password")

    # Create target as online player with separate mock_writer
    target_writer = MagicMock()
    target_writer.write = MagicMock()
    target_writer.drain = AsyncMock()
    target = make_player("target", zone, target_writer, mock_reader, is_admin=False)

    await cmd_promote(player, "target")

    # Verify live state updated
    assert target.is_admin is True

    # Verify database updated
    data = load_player_data("target")
    assert data is not None
    assert data["is_admin"] is True

    # Verify messages sent
    builder_output = "".join(
        call.args[0] for call in player.writer.write.call_args_list
    )
    assert "is now an admin" in builder_output

    target_output = "".join(call.args[0] for call in target_writer.write.call_args_list)
    assert "granted admin status" in target_output


@pytest.mark.asyncio
async def test_demote_self(zone, mock_writer, mock_reader, db):
    """demote self shows error."""
    player = make_player("builder", zone, mock_writer, mock_reader, is_admin=True)
    create_account("builder", "password")

    await cmd_demote(player, "builder")

    output = "".join(call.args[0] for call in player.writer.write.call_args_list)
    assert "can't demote yourself" in output


@pytest.mark.asyncio
async def test_demote_online_player(zone, mock_writer, mock_reader, db):
    """demote online player updates both database and live state."""
    player = make_player("builder", zone, mock_writer, mock_reader, is_admin=True)
    create_account("builder", "password")
    create_account("target", "password")
    set_admin("target", True)

    # Create target as online admin player with separate mock_writer
    target_writer = MagicMock()
    target_writer.write = MagicMock()
    target_writer.drain = AsyncMock()
    target = make_player("target", zone, target_writer, mock_reader, is_admin=True)

    await cmd_demote(player, "target")

    # Verify live state updated
    assert target.is_admin is False

    # Verify database updated
    data = load_player_data("target")
    assert data is not None
    assert data["is_admin"] is False

    # Verify messages sent
    builder_output = "".join(
        call.args[0] for call in player.writer.write.call_args_list
    )
    assert "no longer an admin" in builder_output

    target_output = "".join(call.args[0] for call in target_writer.write.call_args_list)
    assert "revoked" in target_output


@pytest.mark.asyncio
async def test_demote_no_args(zone, mock_writer, mock_reader, db):
    """demote with no args shows usage."""
    player = make_player("builder", zone, mock_writer, mock_reader, is_admin=True)
    create_account("builder", "password")

    await cmd_demote(player, "")

    output = "".join(call.args[0] for call in player.writer.write.call_args_list)
    assert "Usage:" in output


@pytest.mark.asyncio
async def test_demote_offline_player(zone, mock_writer, mock_reader, db):
    """demote offline player updates database."""
    admin_player = make_player("admin", zone, mock_writer, mock_reader, is_admin=True)
    create_account("admin", "password")
    create_account("target", "password")
    set_admin("target", True)

    await cmd_demote(admin_player, "target")

    # Verify database updated
    data = load_player_data("target")
    assert data is not None
    assert data["is_admin"] is False

    output = "".join(call.args[0] for call in admin_player.writer.write.call_args_list)
    assert "no longer an admin" in output


@pytest.mark.asyncio
async def test_demote_nonexistent(zone, mock_writer, mock_reader, db):
    """demote nonexistent account shows error."""
    player = make_player("builder", zone, mock_writer, mock_reader, is_admin=True)
    create_account("builder", "password")

    await cmd_demote(player, "nobody")

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


@pytest.mark.asyncio
async def test_promote_already_admin(zone, mock_writer, mock_reader, db):
    """promote already-admin account shows message."""
    player = make_player("builder", zone, mock_writer, mock_reader, is_admin=True)
    create_account("builder", "password")
    create_account("target", "password")
    set_admin("target", True)

    await cmd_promote(player, "target")

    output = "".join(call.args[0] for call in player.writer.write.call_args_list)
    assert "already an admin" in output


@pytest.mark.asyncio
async def test_demote_not_admin(zone, mock_writer, mock_reader, db):
    """demote non-admin account shows message."""
    player = make_player("builder", zone, mock_writer, mock_reader, is_admin=True)
    create_account("builder", "password")
    create_account("target", "password")

    await cmd_demote(player, "target")

    output = "".join(call.args[0] for call in player.writer.write.call_args_list)
    assert "not an admin" in output
