using System.Collections.Generic;
using System.Linq;
using CounterDrone.Core.Algorithms;
using CounterDrone.Core.Models;
using Xunit;

namespace CounterDrone.Core.Tests
{
    public class DefensePlannerTests
    {
        private static readonly List<AmmunitionSpec> TestAmmo = DefaultAmmunition.GetAll();

        private static List<FireUnit> MakeGroundUnits(int count = 2)
        {
            var units = new List<FireUnit>();
            for (int i = 0; i < count; i++)
                units.Add(new FireUnit
                {
                    Id = $"u{i}",
                    Type = PlatformType.GroundBased,
                    Position = new Vector3(5000 + i * 100, 0, 50),
                    GunCount = 4,
                    ChannelsPerGun = 4,
                    ChannelInterval = 1f,
                    MuzzleVelocity = 800f,
                    TotalMunitions = 16,
                    Cooldown = 5f,
                    AmmoTypes = new List<AerosolType> { AerosolType.InertGas, AerosolType.ActiveMaterial, AerosolType.ActiveFuel },
                });
            return units;
        }

        private DroneGroup MakeThreat(PowerType powerType = PowerType.Piston, float speed = 120f)
        {
            return new DroneGroup
            {
                GroupId = "default",
                Target = new TargetConfig
                {
                    TargetType = (int)TargetType.Piston,
                    PowerType = (int)powerType,
                    Quantity = 1,
                    TypicalSpeed = speed,
                    TypicalAltitude = 500,
                },
                Waypoints = new List<Waypoint>
                {
                    new Waypoint { PosX = 0, PosY = 500, PosZ = 100, Altitude = 500, Speed = speed },
                    new Waypoint { PosX = 10000, PosY = 500, PosZ = 100, Altitude = 500, Speed = speed },
                },
            };
        }

        // ═══════════════════════════════════════
        // 威胁排序
        // ═══════════════════════════════════════

        [Fact]
        public void ThreatPriority_SpeedMatters()
        {
            var slow = MakeThreat(PowerType.Piston, 60);
            slow.Target.TargetType = (int)TargetType.Piston;
            slow.ArrivalTime = 100;
            slow.ThreatIndex = CalcIndex(slow.Target);

            var fast = MakeThreat(PowerType.Piston, 500);
            fast.Target.TargetType = (int)TargetType.Piston;
            fast.ArrivalTime = 100;
            fast.ThreatIndex = CalcIndex(fast.Target);

            Assert.True(fast.Priority > slow.Priority, $"fast={fast.Priority} slow={slow.Priority}");
        }

        [Fact]
        public void ThreatPriority_TypeMatters()
        {
            var rotor = MakeThreat(PowerType.Electric, 200);
            rotor.Target.TargetType = (int)TargetType.Rotor;
            rotor.ArrivalTime = 50;
            rotor.ThreatIndex = CalcIndex(rotor.Target);

            var jet = MakeThreat(PowerType.Piston, 200);
            jet.Target.TargetType = (int)TargetType.HighSpeed;
            jet.ArrivalTime = 50;
            jet.ThreatIndex = CalcIndex(jet.Target);

            Assert.True(jet.ThreatIndex > rotor.ThreatIndex, $"jet={jet.ThreatIndex} rotor={rotor.ThreatIndex}");
        }

        private static float CalcIndex(TargetConfig t)
        {
            var coef = new Dictionary<TargetType, float>
            {
                { TargetType.HighSpeed, 4f }, { TargetType.FixedWing, 2f },
                { TargetType.Piston, 2f }, { TargetType.Rotor, 1f }, { TargetType.Electric, 1f },
            }.GetValueOrDefault((TargetType)t.TargetType, 1f);
            return coef * (float)t.TypicalSpeed / 60f;
        }

        // ═══════════════════════════════════════
        // 弹药匹配
        // ═══════════════════════════════════════

        [Fact] public void Piston_InertGas() { Assert.Equal(AerosolType.InertGas, Plan(PowerType.Piston).Best.Assignments[0].AmmoType); }
        [Fact] public void Jet_ActiveMaterial() { Assert.Equal(AerosolType.ActiveMaterial, Plan(PowerType.Jet, 500).Best.Assignments[0].AmmoType); }
        [Fact] public void Electric_InertGas() { Assert.Equal(AerosolType.InertGas, Plan(PowerType.Electric).Best.Assignments[0].AmmoType); }

        // ═══════════════════════════════════════
        // 通道分配
        // ═══════════════════════════════════════

        [Fact]
        public void SingleUnit_AllChannelsUsed_IfNeeded()
        {
            var result = Plan(PowerType.Piston, 60);
            var events = result.Best.MergedSchedule;
            Assert.True(events.Count >= 3, $"count={events.Count}");
            // 通道间隔错开，但同轮无 cooldown 级延迟
            float first = events.Min(e => e.FireTime);
            float last = events.Max(e => e.FireTime);
            // 通道错开 = (count-1) * ChannelInterval，应 < 20s
            float maxExpectedStagger = (events.Count - 1) * 1f + 2f;
            Assert.True(last - first < maxExpectedStagger,
                $"spread={last-first:F1}s, expected<{maxExpectedStagger:F1}s");
        }

        [Fact]
        public void NoThreats_Empty() { Assert.Equal(0, new DefaultDefensePlanner(TestAmmo).Plan(new(), new(), new CombatScene()).Best.ThreatsEngaged); }

        [Fact]
        public void ChannelInterval_StaggersWithinSalvo()
        {
            var result = Plan(PowerType.Piston, 60);
            var events = result.Best.MergedSchedule.OrderBy(e => e.FireTime).ToList();
            // 同一单元通道有 ChannelInterval=1s 错开
            Assert.True(events.Count >= 2);
        }

        [Fact]
        public void MultiUnit_PoolsChannels()
        {
            // 2 个单元 = 32 通道
            var result = new DefaultDefensePlanner(TestAmmo).Plan(
                MakeGroundUnits(2), new() { MakeThreat(PowerType.Piston, 200) }, new CombatScene());
            Assert.True(result.Best.ThreatsEngaged > 0);
            Assert.True(result.Best.MergedSchedule.Count > 1);
        }

        [Fact]
        public void OneSalvo_NoCooldownNeeded()
        {
            // 16 通道足够覆盖慢速目标
            var result = Plan(PowerType.Piston, 60);
            var events = result.Best.MergedSchedule;
            float first = events.Min(e => e.FireTime);
            float last = events.Max(e => e.FireTime);
            // 最后一个事件 - 第一个事件 < Cooldown → 同一轮
            Assert.True(last - first < 5f, $"spread={last - first:F1}s, should be < 5s cooldown");
        }

        [Fact]
        public void TotalMunitions_CapsFirepower()
        {
            var units = new List<FireUnit>
            {
                new FireUnit { Id = "u0", Type = PlatformType.GroundBased,
                    Position = new Vector3(5000, 0, 50), GunCount = 4, ChannelsPerGun = 4,
                    MuzzleVelocity = 800, TotalMunitions = 2, // 只够打 2 发
                    AmmoTypes = new() { AerosolType.InertGas } },
            };
            var result = new DefaultDefensePlanner(TestAmmo).Plan(
                units, new() { MakeThreat(PowerType.Piston, 200) }, new CombatScene());
            Assert.True(result.Best.ThreatsEngaged > 0);
            Assert.True(result.Best.Assignments.Sum(a => a.RoundsFired) <= 2);
        }

        // ═══════════════════════════════════════
        // 过滤与边界
        // ═══════════════════════════════════════

        [Fact]
        public void IncompatibleAmmo_NotEngaged()
        {
            var units = new List<FireUnit>
            {
                new FireUnit { Id = "u0", Type = PlatformType.GroundBased,
                    Position = new Vector3(5000, 0, 50), GunCount = 4, ChannelsPerGun = 4,
                    MuzzleVelocity = 800, TotalMunitions = 16,
                    AmmoTypes = new() { AerosolType.ActiveMaterial } },
            };
            var result = new DefaultDefensePlanner(TestAmmo).Plan(
                units, new() { MakeThreat(PowerType.Piston) }, new CombatScene());
            Assert.Equal(0, result.Best.ThreatsEngaged);
        }

        [Fact]
        public void NoUnits_Unengaged() { Assert.Equal(1, Plan(unitCount: 0).Best.ThreatsUnengaged); }

        [Fact]
        public void FireTime_Positive()
        {
            var result = Plan(PowerType.Piston, 60);
            Assert.NotEmpty(result.Best.MergedSchedule);
            Assert.All(result.Best.MergedSchedule, fe => Assert.True(fe.FireTime > 0));
        }

        [Fact]
        public void FireTime_BeforeArrival()
        {
            var threat = MakeThreat(PowerType.Piston, 120);
            var result = new DefaultDefensePlanner(TestAmmo).Plan(
                MakeGroundUnits(1), new() { threat }, new CombatScene());
            float arrival = threat.GetArrivalTime();
            Assert.All(result.Best.MergedSchedule, fe =>
                Assert.True(fe.FireTime < arrival, $"FireTime={fe.FireTime:F0} >= {arrival:F0}"));
        }

        [Fact]
        public void MultiThreat_BothEngaged()
        {
            var threats = new List<DroneGroup> { MakeThreat(PowerType.Piston, 120), MakeThreat(PowerType.Jet, 300) };
            threats[0].GroupId = "g0"; threats[1].GroupId = "g1";
            var result = new DefaultDefensePlanner(TestAmmo).Plan(MakeGroundUnits(2), threats, new CombatScene());
            Assert.Equal(2, result.Best.ThreatsEngaged);
        }

        [Fact]
        public void Critical_HasAssignments()
        {
            var result = Plan(PowerType.Piston, 120);
            Assert.True(result.Critical.Assignments.Count > 0);
            Assert.True(result.Critical.OverallProbability >= 0.4f);
        }

        [Fact]
        public void Best_ProbabilityHigherThanCritical()
        {
            var result = Plan(PowerType.Piston, 120);
            Assert.True(result.Best.OverallProbability >= result.Critical.OverallProbability,
                $"Best={result.Best.OverallProbability:P0} Critical={result.Critical.OverallProbability:P0}");
        }

        // ═══════════════════════════════════════
        // 空基
        // ═══════════════════════════════════════

        [Fact]
        public void AirBased_Engaged()
        {
            var units = new List<FireUnit>
            {
                new FireUnit { Id = "u0", Type = PlatformType.AirBased,
                    Position = new Vector3(3000, 2500, -2000), GunCount = 4, ChannelsPerGun = 4,
                    CruiseSpeed = 55, ReleaseAltitude = 1500, TotalMunitions = 16,
                    AmmoTypes = new() { AerosolType.InertGas } },
            };
            var result = new DefaultDefensePlanner(TestAmmo).Plan(units, new() { MakeThreat() }, new CombatScene());
            Assert.True(result.Best.ThreatsEngaged > 0);
        }

        // ═══════════════════════════════════════
        // 集成验证
        // ═══════════════════════════════════════

        [Fact]
        public void FullPipeline_Piston_VerifiesRoundAllocation()
        {
            var units = MakeGroundUnits(1); // 1 单元 16 通道
            var threat = MakeThreat(PowerType.Piston, 200);
            threat.Waypoints.Clear();
            threat.Waypoints.Add(new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 });
            threat.Waypoints.Add(new Waypoint { PosX = 20000, PosY = 500, PosZ = 0, Speed = 200 });
            var result = new DefaultDefensePlanner(TestAmmo).Plan(units, new() { threat }, new CombatScene { WindSpeed = 3 });
            Assert.True(result.Best.MergedSchedule.Count >= 8,
                $"只有 {result.Best.MergedSchedule.Count} 发，预期 >= 8");
            Assert.All(result.Best.MergedSchedule, fe =>
                Assert.True(fe.PlatformIndex >= 0 && fe.PlatformIndex < units.Count));
        }

        // ═══════════════════════════════════════
        // helpers
        // ═══════════════════════════════════════

        private PlannerResult Plan(PowerType power = PowerType.Piston, float speed = 120f, int unitCount = 1)
        {
            return new DefaultDefensePlanner(TestAmmo).Plan(
                unitCount > 0 ? MakeGroundUnits(unitCount) : new List<FireUnit>(),
                new() { MakeThreat(power, speed) },
                new CombatScene());
        }
    }
}
