from unittest.mock import patch

import pytest
from django.contrib.auth import get_user_model

from boardgames.models import BoardGame, BoardGameVariant

User = get_user_model()


@pytest.mark.django_db
def test_board_game_variant_creation():
    game = BoardGame.objects.create(title="Test Game")
    variant = BoardGameVariant.objects.create(
        name="Test Variant",
        board_game=game,
        description="A test variant",
    )
    assert variant.name == "Test Variant"
    assert variant.board_game == game
    assert variant.description == "A test variant"
    assert variant.uuid is not None


@pytest.mark.django_db
def test_board_game_variant_str():
    game = BoardGame.objects.create(title="Test Game")
    variant = BoardGameVariant.objects.create(
        name="Test Variant",
        board_game=game,
    )
    assert str(variant) == "Test Variant (Test Game)"


@pytest.mark.django_db
def test_board_game_variant_optional_description():
    game = BoardGame.objects.create(title="Test Game")
    variant = BoardGameVariant.objects.create(
        name="Test Variant",
        board_game=game,
    )
    assert variant.description is None


@pytest.mark.django_db
def test_board_game_variant_related_name():
    game = BoardGame.objects.create(title="Test Game")
    variant1 = BoardGameVariant.objects.create(
        name="Variant 1",
        board_game=game,
    )
    variant2 = BoardGameVariant.objects.create(
        name="Variant 2",
        board_game=game,
    )
    assert list(game.variants.all()) == [variant1, variant2]


@pytest.mark.django_db
def test_board_game_variant_cascade_delete():
    game = BoardGame.objects.create(title="Test Game")
    variant = BoardGameVariant.objects.create(
        name="Test Variant",
        board_game=game,
    )
    game.delete()
    assert BoardGameVariant.objects.count() == 0


def _mock_bgg_game(bggeek_id, title, expansions=None):
    """Build a fake BGG game object shape used by lookup_boardgame_from_bgg."""
    class FakeGame:
        id = bggeek_id
        name = title
        description = f"Description of {title}"
        yearpublished = 2020
        image = "https://example.com/cover.jpg"
        minplayers = 1
        maxplayers = 4
        minage = 8
        rating_average = 7.5
        bgg_rank = 100
        playingtime = 60
        mechanics = []
        categories = []
        families = []
        designers = []
        publishers = []

        @property
        def expansions(self):
            if expansions is None:
                return []
            return expansions

    return FakeGame()


def _mock_bgg_client(return_game):
    """Return a callable that creates a fake BGGClient instance."""
    class FakeBGGClient:
        def __init__(self, access_token=None):
            pass

        def game(self, game_id=None, name=None):
            return return_game

    return FakeBGGClient()


@pytest.mark.django_db
@patch("boardgames.models.requests.get")
@patch("boardgames.sources.bgg.BGGClient")
def test_find_or_create_links_expansions(mock_bgg, mock_get):
    exp1 = type("Thing", (), {"id": 200, "name": "Expansion 1"})()
    exp2 = type("Thing", (), {"id": 201, "name": "Expansion 2"})()
    base_game = _mock_bgg_game("100", "Base Game", expansions=[exp1, exp2])
    exp1_game = _mock_bgg_game("200", "Expansion 1")
    exp2_game = _mock_bgg_game("201", "Expansion 2")
    mock_bgg.side_effect = [
        _mock_bgg_client(base_game),
        _mock_bgg_client(exp1_game),
        _mock_bgg_client(exp2_game),
    ]
    mock_get.return_value.status_code = 200
    mock_get.return_value.content = b"fake_image_data"

    game = BoardGame.find_or_create("100")
    assert game.title == "Base Game"

    expansions = BoardGame.objects.filter(expansion_for_boardgame=game)
    assert expansions.count() == 2
    assert {e.title for e in expansions} == {"Expansion 1", "Expansion 2"}


@pytest.mark.django_db
@patch("boardgames.sources.bgg.BGGClient")
def test_find_or_create_skips_expansions_for_existing_game(mock_bgg):
    game = BoardGame.objects.create(title="Base Game", bggeek_id="100")
    mock_bgg.assert_not_called()

    result = BoardGame.find_or_create("100")
    assert result == game


@pytest.mark.django_db
@patch("boardgames.models.requests.get")
@patch("boardgames.sources.bgg.BGGClient")
def test_fetch_and_link_expansions(mock_bgg, mock_get):
    game = BoardGame.objects.create(title="Base Game", bggeek_id="100")
    exp_game = _mock_bgg_game("200", "Expansion 1")
    mock_bgg.side_effect = [_mock_bgg_client(exp_game)]
    mock_get.return_value.status_code = 200
    mock_get.return_value.content = b"fake_image_data"

    from boardgames.utils import fetch_and_link_expansions

    fetch_and_link_expansions(game, [{"id": 200, "name": "Expansion 1"}])

    expansion = BoardGame.objects.get(bggeek_id="200")
    assert expansion.expansion_for_boardgame == game


@pytest.mark.django_db
@patch("boardgames.models.requests.get")
@patch("boardgames.sources.bgg.BGGClient")
def test_fix_metadata_links_expansions(mock_bgg, mock_get):
    game = BoardGame.objects.create(title="Base Game", bggeek_id="100")
    exp_thing = type("Thing", (), {"id": 200, "name": "Expansion 1"})()
    base_with_exp = _mock_bgg_game("100", "Base Game", expansions=[exp_thing])
    exp_game = _mock_bgg_game("200", "Expansion 1")
    # first call = fix_metadata -> lookup_boardgame_from_bgg(lookup_id='100')
    # second call = find_or_create inside fetch_and_link_expansions for expansion
    mock_bgg.side_effect = [
        _mock_bgg_client(base_with_exp),
        _mock_bgg_client(exp_game),
    ]
    mock_get.return_value.status_code = 200
    mock_get.return_value.content = b"fake_image_data"

    game.fix_metadata(force_update=True)

    expansion = BoardGame.objects.get(bggeek_id="200")
    assert expansion.expansion_for_boardgame == game


@pytest.mark.django_db
@patch("boardgames.sources.bgg.BGGClient")
def test_management_command_fetch_expansions_dry_run(mock_bgg, capsys):
    from django.core.management import call_command

    game = BoardGame.objects.create(title="Base Game", bggeek_id="100")
    exp_thing = type("Thing", (), {"id": 200, "name": "Expansion 1"})()
    base_with_exp = _mock_bgg_game("100", "Base Game", expansions=[exp_thing])
    mock_bgg.side_effect = [_mock_bgg_client(base_with_exp)]

    call_command("fetch_expansions")
    captured = capsys.readouterr()
    assert "Would link 1 expansions" in captured.out

    assert BoardGame.objects.filter(bggeek_id="200").count() == 0


@pytest.mark.django_db
@patch("boardgames.models.requests.get")
@patch("boardgames.sources.bgg.BGGClient")
def test_management_command_fetch_expansions_commit(mock_bgg, mock_get, capsys):
    from django.core.management import call_command

    game = BoardGame.objects.create(title="Base Game", bggeek_id="100")
    exp_thing = type("Thing", (), {"id": 200, "name": "Expansion 1"})()
    exp_game = _mock_bgg_game("200", "Expansion 1")
    base_with_exp = _mock_bgg_game("100", "Base Game", expansions=[exp_thing])
    mock_bgg.side_effect = [
        _mock_bgg_client(base_with_exp),
        _mock_bgg_client(exp_game),
    ]
    mock_get.return_value.status_code = 200
    mock_get.return_value.content = b"fake_image_data"

    call_command("fetch_expansions", commit=True)
    captured = capsys.readouterr()
    assert "Updated 1 games" in captured.out

    expansion = BoardGame.objects.get(bggeek_id="200")
    assert expansion.expansion_for_boardgame == game
