using GerbilManagerWebAPI.Genetics;

namespace GerbilManager.Tests
{
    /// <summary>
    /// Exact-math checks for the inbreeding coefficient (Inzuchtkoeffizient) against
    /// the textbook reference values for canonical pedigrees.
    /// </summary>
    public class PedigreeCalculatorTests
    {
        private const int Precision = 10;

        [Fact]
        public void UnknownAncestry_IsZero()
        {
            var ped = new PedigreeBuilder();
            var lonely = ped.Add("Lonely");
            var calc = ped.Build();

            var result = calc.ForIndividual(lonely);

            Assert.Equal(0.0, result.Coefficient, Precision);
            Assert.Equal(0, result.GenerationsAvailable);
            Assert.Empty(result.CommonAncestors);
        }

        [Fact]
        public void OneKnownParent_IsZero()
        {
            var ped = new PedigreeBuilder();
            var sire = ped.Add("Sire");
            var calc = ped.Build();

            // Only one parent known → no inbreeding can be established.
            var result = calc.ForOffspringOf(sire, null);

            Assert.Equal(0.0, result.Coefficient, Precision);
            Assert.Empty(result.CommonAncestors);
        }

        [Fact]
        public void ParentOffspring_IsQuarter()
        {
            // Z is the offspring of P mated with its own daughter C.
            var ped = new PedigreeBuilder();
            var p = ped.Add("P");
            var mate = ped.Add("Mate");
            var c = ped.AddChild("C", p, mate);

            var calc = ped.Build();
            var result = calc.ForOffspringOf(p, c);

            Assert.Equal(0.25, result.Coefficient, Precision);
            Assert.Equal(25.0, result.Percent, Precision);
            var ancestor = Assert.Single(result.CommonAncestors);
            Assert.Equal(p, ancestor.Id);
            Assert.Equal(0.25, ancestor.Contribution, Precision);
        }

        [Fact]
        public void FullSiblings_IsQuarter()
        {
            // Parents S and D are full siblings (both children of GF and GM).
            var ped = new PedigreeBuilder();
            var gf = ped.Add("GF");
            var gm = ped.Add("GM");
            var s = ped.AddChild("S", gf, gm);
            var d = ped.AddChild("D", gf, gm);

            var calc = ped.Build();
            var result = calc.ForOffspringOf(s, d);

            Assert.Equal(0.25, result.Coefficient, Precision);
            Assert.Equal(2, result.CommonAncestors.Count);
            Assert.All(result.CommonAncestors, a => Assert.Equal(0.125, a.Contribution, Precision));
        }

        [Fact]
        public void HalfSiblings_IsEighth()
        {
            // Parents S and D share only GF (other parents unrelated).
            var ped = new PedigreeBuilder();
            var gf = ped.Add("GF");
            var x = ped.Add("X");
            var y = ped.Add("Y");
            var s = ped.AddChild("S", gf, x);
            var d = ped.AddChild("D", gf, y);

            var calc = ped.Build();
            var result = calc.ForOffspringOf(s, d);

            Assert.Equal(0.125, result.Coefficient, Precision);
            var ancestor = Assert.Single(result.CommonAncestors);
            Assert.Equal(gf, ancestor.Id);
            Assert.Equal(0.125, ancestor.Contribution, Precision);
        }

        [Fact]
        public void FirstCousins_IsSixteenth()
        {
            // Parents S and D are first cousins: their respective parents A1 and B1
            // are full siblings (children of GF and GM).
            var ped = new PedigreeBuilder();
            var gf = ped.Add("GF");
            var gm = ped.Add("GM");
            var a1 = ped.AddChild("A1", gf, gm);
            var b1 = ped.AddChild("B1", gf, gm);
            var x = ped.Add("X");
            var y = ped.Add("Y");
            var s = ped.AddChild("S", a1, x);
            var d = ped.AddChild("D", b1, y);

            var calc = ped.Build();
            var result = calc.ForOffspringOf(s, d);

            Assert.Equal(0.0625, result.Coefficient, Precision);
            Assert.Equal(2, result.CommonAncestors.Count);
            Assert.All(result.CommonAncestors, a => Assert.Equal(0.03125, a.Contribution, Precision));
        }

        [Fact]
        public void DeepCommonAncestry_SumsAllDisjointPaths()
        {
            // Full siblings S and D whose shared parents (GF, GM) are themselves full
            // siblings (children of GGF, GGM). GF and GM are NOT inbred (their parents
            // are unrelated founders, so F_GF = F_GM = 0), but the great-grandparents
            // are reached by additional disjoint paths:
            //   GF + GM:  2 * (1/2)^3            = 0.25
            //   GGF:      2 * (1/2)^5            = 0.0625   (S-GF-GGF / D-GM-GGF and swap)
            //   GGM:      2 * (1/2)^5            = 0.0625
            //   ---------------------------------------------
            //   F = 0.375   (verified against the recursive kinship method)
            var ped = new PedigreeBuilder();
            var ggf = ped.Add("GGF");
            var ggm = ped.Add("GGM");
            var gf = ped.AddChild("GF", ggf, ggm);
            var gm = ped.AddChild("GM", ggf, ggm);
            var s = ped.AddChild("S", gf, gm);
            var d = ped.AddChild("D", gf, gm);

            var calc = ped.Build();
            var result = calc.ForOffspringOf(s, d);

            Assert.Equal(0.375, result.Coefficient, Precision);
            // Four common ancestors: GF, GM, GGF, GGM.
            Assert.Equal(4, result.CommonAncestors.Count);
            Assert.Equal(0.375, result.CommonAncestors.Sum(a => a.Contribution), Precision);
            // Deepest known generation above the offspring: S/D (1) → GF/GM (2) → GGF/GGM (3).
            Assert.Equal(3, result.GenerationsAvailable);
        }

        [Fact]
        public void GenerationsAvailable_CountsDeepestKnownGeneration()
        {
            var ped = new PedigreeBuilder();
            var gf = ped.Add("GF");
            var gm = ped.Add("GM");
            var father = ped.AddChild("Father", gf, gm);
            var mother = ped.Add("Mother");

            var calc = ped.Build();
            var result = calc.ForOffspringOf(father, mother);

            // Father side reaches grandparents (2 generations); mother side reaches 1.
            Assert.Equal(2, result.GenerationsAvailable);
        }

        /// <summary>Small helper to assemble a pedigree of plain GUIDs for the calculator.</summary>
        private sealed class PedigreeBuilder
        {
            private readonly Dictionary<Guid, (Guid? Father, Guid? Mother)> _parents = new();
            private readonly Dictionary<Guid, string> _names = new();

            public Guid Add(string name)
            {
                var id = Guid.NewGuid();
                _parents[id] = (null, null);
                _names[id] = name;
                return id;
            }

            public Guid AddChild(string name, Guid father, Guid mother)
            {
                var id = Add(name);
                _parents[id] = (father, mother);
                return id;
            }

            public PedigreeCalculator Build() => new(_parents, _names);
        }
    }
}
