using CoreLibrary;
using CoreLibrary.Exceptions;
using System.Reflection;
using Xunit;

namespace UnitTesting
{
    public class PlateauUT
    {
        [Fact]
        public void TestConstructorValid()
        {
            Plateau plateau = new Plateau(4,12);
            Assert.NotNull(plateau);
            Assert.Equal(1, plateau.Tour);
            Assert.False(plateau.Victoire);
        }

        [Fact]
        public void TestConstructorInvalid()
        {
            Assert.Throws<TailleCodeException>(() => new Plateau(0, 10));
            Assert.Throws<TailleGrilleException>(() => new Plateau(3, 0));
        }

        [Fact]
        public void TestEstCompletTrue()
        {
            Plateau plateau = new Plateau(4, 3);
            Code code = new Code([new Jeton(Couleur.ROUGE), new Jeton(Couleur.BLEU), new Jeton(Couleur.BLANC), new Jeton(Couleur.JAUNE)]);
            plateau.AjouterCode(code);
            plateau.AjouterCode(code);
            plateau.AjouterCode(code);

            bool estComplet = plateau.EstComplet();

            Assert.True(estComplet);
        }

        [Fact]
        public void TestEstCompletFalse()
        {
            Plateau plateau = new Plateau(4, 3);
            Code code = new Code([new Jeton(Couleur.ROUGE), new Jeton(Couleur.BLEU), new Jeton(Couleur.BLANC), new Jeton(Couleur.JAUNE)]);
            plateau.AjouterCode(code);
            plateau.AjouterCode(code);

            bool estComplet = plateau.EstComplet();

            Assert.False(estComplet);
        }

        [Fact]
        public void TestAjouterCodeValide()
        {
            Plateau plateau = new Plateau(4, 10);
            Code code = new Code([new Jeton(Couleur.BLEU), new Jeton(Couleur.BLANC), new Jeton(Couleur.JAUNE), new Jeton(Couleur.BLANC)]);

            plateau.AjouterCode(code);

            Assert.Equal(2, plateau.Tour);
        }

        [Fact]
        public void TestAjouterCodeTailleIncorrecte()
        {
            Plateau plateau = new Plateau(4, 10);
            Code code = new Code([new Jeton(Couleur.BLEU), new Jeton(Couleur.BLANC), new Jeton(Couleur.JAUNE), new Jeton(Couleur.BLANC), new Jeton(Couleur.JAUNE)]); 
            
            Assert.Throws<CodeInvalideException>(() => plateau.AjouterCode(code));
        }

        [Fact]
        public void TestAjouterCodeIncomplet()
        {
            Plateau plateau = new Plateau(4, 10);
            Code code = new Code(4);

            Assert.Throws<CodeIncompletException>(() => plateau.AjouterCode(code));
        }

        [Fact]
        public void TestAjouterCodeBonCode()
        {
            Plateau plateau = new Plateau(4, 10);

            Type type = typeof(Plateau);

            FieldInfo? fieldInfo = type.GetField("codeSecret", BindingFlags.NonPublic | BindingFlags.Instance);
            Assert.NotNull(fieldInfo);

            Code? codeSecret = (Code?)fieldInfo.GetValue(plateau);

            Assert.NotNull(codeSecret);
            plateau.AjouterCode(codeSecret);
            Assert.True(plateau.Victoire);
        }

        [Fact]
        public void TestEstBonCodeTailleException()
        {
            Plateau plateau = new Plateau(3, 5);
            Code code = new Code(4);

            Assert.Throws<CodeInvalideException>(() => plateau.EstBonCode(code));
        }

        [Fact]
        public void TestEstBonCodeIncomplet()
        {
            Plateau plateau = new Plateau(3, 5);
            Code code = new Code(3);

            Assert.Throws<CodeIncompletException>(() => plateau.EstBonCode(code));
        }

        [Fact]
        public void TestEstBonCodeTrue()
        {
            Plateau plateau = new Plateau(4, 10);

            Type type = typeof(Plateau);

            FieldInfo? fieldInfo = type.GetField("codeSecret", BindingFlags.NonPublic | BindingFlags.Instance);
            Assert.NotNull(fieldInfo);

            Code? codeSecret = (Code?)fieldInfo.GetValue(plateau);

            Assert.NotNull(codeSecret);
            Assert.True(plateau.EstBonCode(codeSecret));
        }

        [Fact]
        public void TestEstBonCodeFalse()
        {
            List<Couleur> couleurs = new List<Couleur>((Couleur[])Enum.GetValues(typeof(Couleur)));

            Plateau plateau = new Plateau(4, 10);

            Type type = typeof(Plateau);

            FieldInfo? fieldInfo = type.GetField("codeSecret", BindingFlags.NonPublic | BindingFlags.Instance);
            Assert.NotNull(fieldInfo);

            Code? codeSecret = (Code?)fieldInfo.GetValue(plateau);
            Assert.NotNull(codeSecret);

            Jeton[] jetons = codeSecret.Jetons().Where(jeton => jeton.HasValue).Select(jeton => jeton!.Value).ToArray();

            Couleur couleurJeton = jetons[0].Couleur;
            int indice = couleurs.IndexOf(couleurJeton) + 1;
            if (indice >= couleurs.Count)
                indice = 0;

            jetons[0] = new Jeton(couleurs[indice]);

            Code code = new Code(jetons);

            Assert.False(plateau.EstBonCode(code));
        }

        [Fact]
        public void TestEstBonCodeAucunIndicateur()
        {
            List<Couleur> couleurs = new List<Couleur>((Couleur[])Enum.GetValues(typeof(Couleur)));
            Plateau plateau = new Plateau(4, 10);
            Type type = typeof(Plateau);

            FieldInfo? fieldInfo = type.GetField("codeSecret", BindingFlags.NonPublic | BindingFlags.Instance);
            Assert.NotNull(fieldInfo);

            Code? codeSecret = (Code?)fieldInfo.GetValue(plateau);
            Assert.NotNull(codeSecret);

            Jeton[] jetons = codeSecret.Jetons().Where(jeton => jeton.HasValue).Select(jeton => jeton!.Value).ToArray();

            for (int i=0; i<jetons.Length; ++i)
            {
                Couleur couleurJeton = jetons[i].Couleur;
                couleurs.Remove(couleurJeton);
            }

            Code code = new Code(
                [
                    new Jeton(couleurs[0]),
                    new Jeton(couleurs[0]),
                    new Jeton(couleurs[0]),
                    new Jeton(couleurs[0])
                ]
             );

            Assert.False(plateau.EstBonCode(code));
        }

        [Fact]
        public void TestGrilleValid()
        {
            Plateau plateau = new Plateau(4, 3);
            Code code1 = new Code([new Jeton(Couleur.BLEU), new Jeton(Couleur.BLANC), new Jeton(Couleur.JAUNE), new Jeton(Couleur.BLANC)]);
            Code code2 = new Code([new Jeton(Couleur.VERT), new Jeton(Couleur.JAUNE), new Jeton(Couleur.ROUGE), new Jeton(Couleur.NOIR)]);

            plateau.AjouterCode(code1);
            plateau.AjouterCode(code2);

            IEnumerable<IEnumerable<Jeton?>> grille = plateau.Grille();

            Assert.Equal(4, grille.First().Count());
            Assert.Equal(4, grille.Last().Count());
            Assert.Equal(code1.Jetons(), grille.ElementAt(0));
            Assert.Equal(code2.Jetons(), grille.ElementAt(1));

        }

        [Fact]
        public void TestGrilleEmpty()
        {
            Plateau plateau = new Plateau(4, 3);

            IEnumerable<IEnumerable<Jeton?>> grille = plateau.Grille();

            foreach (IEnumerable<Jeton?> tour in grille)
            {
                Assert.All(tour, jeton => Assert.Null(jeton));
            }
        }

        [Fact]
        public void TestIndicateursVide()
        {
            Plateau plateau = new Plateau(4, 5);

            foreach(Indicateur[] indicateur in plateau.Indicateurs())
            {
                Assert.Null(indicateur);
            }
        }

        [Fact]
        public void TestIndicateursAjouterDeuxCodes()
        {
            Plateau plateau = new Plateau(4, 5);
            Code code1 = new Code([new Jeton(Couleur.BLEU), new Jeton(Couleur.BLANC), new Jeton(Couleur.JAUNE), new Jeton(Couleur.BLANC)]);
            Code code2 = new Code([new Jeton(Couleur.VERT), new Jeton(Couleur.JAUNE), new Jeton(Couleur.ROUGE), new Jeton(Couleur.NOIR)]);

            plateau.AjouterCode(code1);
            plateau.AjouterCode(code2);

            Assert.NotNull(plateau.Indicateurs().ElementAt(0));
            Assert.NotNull(plateau.Indicateurs().ElementAt(1));
            Assert.Null(plateau.Indicateurs().ElementAt(2));
            Assert.Null(plateau.Indicateurs().ElementAt(3));
            Assert.Null(plateau.Indicateurs().ElementAt(4));
        }
    }
}
