using Moq;
using Models.Game;
using Models.Interfaces;
using System.Reflection;
using static System.Type;

namespace Tests;

public class GameTests
{
    private readonly Mock<IPersistence> _mockPersistence; // Mocking (faking) the persistence layer to allow for testing without a persistent connection
    private readonly Game _game;

    public GameTests()
    {
        _mockPersistence = new Mock<IPersistence>();
        _game = new Game(_mockPersistence.Object);
    }

    private void SetDiceValues(Game game, int value1, int value2)
    {
        var dice1Field = typeof(Game).GetProperty("Dice1", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
        var dice2Field = typeof(Game).GetProperty("Dice2", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

        if (dice1Field != null && dice2Field != null)
        {
            var dice1 = (Dice)dice1Field?.GetValue(game)!;
            var dice2 = (Dice)dice2Field?.GetValue(game)!;

            var valueField = typeof(Dice).GetField("_value", BindingFlags.Instance | BindingFlags.NonPublic);
            valueField?.SetValue(dice1, value1);
            valueField?.SetValue(dice2, value2);
        }
    }

    [Fact]
    public void AddPlayer_ShouldAddPlayerToList()
    {
        var player = new Player();

        _game.AddPlayer(player);

        Assert.Contains(player, _game.Players);
    }

    [Fact]
    public void AddGame_ShouldAddGameToList()
    {
        var game = new Game(_mockPersistence.Object);

        _game.AddGame(game);

        Assert.Contains(game, _game.Games);
    }

    [Fact]
    public void AddMap_ShouldAddMapToList()
    {
        var map = new Map("test_background.png");

        _game.AddMap(map);

        Assert.Contains(map, _game.Maps);
    }

    [Fact]
    public void AddBestScore_ShouldAddBestScoreToList()
    {
        var bestScore = new BestScore(3, 125);

        _game.AddBestScore(bestScore);

        Assert.Contains(bestScore, _game.BestScores);
    }

    [Fact]
    public void LoadData_ShouldLoadDataFromPersistence()
    {
        var players = new List<Player> { new Player() };
        var games = new List<Game> { new Game(_mockPersistence.Object) };
        var maps = new List<Map> { new Map("test_background") };
        var bestScores = new List<BestScore> { new BestScore(1, 26) };

        _mockPersistence.Setup(p => p.LoadData()).Returns((players, games, maps, bestScores));

        _game.LoadData();

        Assert.Equal(players, _game.Players);
        Assert.Equal(games, _game.Games);
        Assert.Equal(maps, _game.Maps);
        Assert.Equal(bestScores, _game.BestScores);
    }

    [Fact]
    public void SaveData_ShouldCallPersistenceSaveData()
    {
        _game.SaveData();

        _mockPersistence.Verify(p => p.SaveData(_game.Players, _game.Games, _game.Maps, _game.BestScores), Times.Once); // Times.Once is to verify if the method is called exactly one time
    }

    [Fact]
    public void InitializeGame_ShouldInitializeGameAndNotTriggerEventWhenNotStarted()
    {
        var player = new Player();
        var map = new Map("test_background");
        bool eventTriggered = false;

        _game.GameStarted += (sender, args) =>
        {
            eventTriggered = true;
        };

        _game.InitializeGame(map, player, false);
        Assert.False(eventTriggered);
        Assert.False(_game.IsRunning);
        Assert.Equal(map, _game.UsedMap);
        Assert.Equal(player, _game.CurrentPlayer);
    }

    [Fact]
    public void InitializeGame_ShouldInitializeGameAndTriggerEventWhenStarted()
    {
        var player = new Player();
        var map = new Map("test_background");
        bool eventTriggered = false;

        _game.GameEnded += (sender, args) =>
        {
            eventTriggered = true;
        };

        _game.InitializeGame(map, player, true);
        Assert.True(eventTriggered);
        Assert.False(_game.IsRunning);
        Assert.Equal(map, _game.UsedMap);
        Assert.Equal(player, _game.CurrentPlayer);
    }

    [Theory]
    [InlineData(Operation.ADDITION, 3, 4, 7)]
    [InlineData(Operation.SUBTRACTION, 6, 4, 2)]
    [InlineData(Operation.MULTIPLICATION, 2, 3, 6)]
    [InlineData(Operation.LOWER, 1, 4, 1)]
    [InlineData(Operation.HIGHER, 2, 5, 5)]
    public void HandlePlayerOperation_ShouldPerformCorrectOperationAndTriggerEvent(Operation operation, int value1, int value2, int expectedResult)
    {
        var player = new Player();
        var map = new Map("background");
        _game.InitializeGame(map, player, false);

        bool eventTriggered = false;
        int actualResult = 0;

        _game.OperationChosen += (sender, args) =>
        {
            eventTriggered = true;
            actualResult = args.Result;
        };

        SetDiceValues(_game, value1, value2);


        _game.HandlePlayerOperation(operation);


        Assert.True(eventTriggered);
        Assert.Equal(expectedResult, actualResult);
    }

    [Fact]
    public void Game_Initialization_SetsMap()
    {
        var player = new Player("test_player");
        var map = new Map("background");

        _game.InitializeGame(map, player);

        Assert.Equal(map, _game.UsedMap);
    }

    [Fact]
    public void Game_Initialization_SetsPlayer()
    {
        var player = new Player("test_player");
        var map = new Map("background");

        _game.InitializeGame(map, player);

        Assert.Equal(player, _game.CurrentPlayer);
    }

    [Fact]
    public void Game_Initialization_SetsDice()
    {
        Player player = new Player();
        Map map = new Map("test_background");

        _game.InitializeGame(map, player);

        Assert.NotNull(_game.Dice1);
        Assert.NotNull(_game.Dice2);
    }

    [Fact]
    public void Game_Initialization_SetsGameRules()
    {
        var game = new Game(_mockPersistence.Object);

        Assert.NotNull(game.GameRules);
    }

    [Fact]
    public void MarkOperationAsChecked_Check_Well()
    {
        var player = new Player();
        var map = new Map("test_background");

        _game.InitializeGame(map, player);

        // Use of reflection to call private method
        var methodInfo = typeof(Game).GetMethod("MarkOperationAsChecked", BindingFlags.NonPublic | BindingFlags.Instance);
        Assert.NotNull(methodInfo);

        var operation = Operation.ADDITION;


        methodInfo.Invoke(_game, new object[] { operation });


        int operationIndex = (int)operation;
        int operationsPerType = 4;
        bool isChecked = false;

        for (int i = operationIndex * operationsPerType; i < (operationIndex + 1) * operationsPerType; i++)
        {
            if (_game.UsedMap.OperationGrid[i].IsChecked)
            {
                isChecked = true;
                break;
            }
        }

        Assert.True(isChecked);
    }
}
