using System.Net;
using Network.Defines;
using Network.NetworkApplication;
using NUnit.Framework;
using UnityEngine;
using Vector3 = UnityEngine.Vector3;

namespace Tests.EditMode.Network
{
    public class ClientGameplayFlowTests
    {
        private static readonly IPEndPoint Sender = new(IPAddress.Loopback, 9300);

        [Test]
        public void ClientGameplayInputFlow_SendShootInput_UsesDedicatedGameplayPathAndReliableLane()
        {
            var reliableTransport = new GameplayFlowFakeTransport();
            var syncTransport = new GameplayFlowFakeTransport();
            var manager = new MessageManager(
                reliableTransport,
                new MainThreadNetworkDispatcher(),
                new DefaultMessageDeliveryPolicyResolver(),
                syncTransport);

            ClientGameplayInputFlow.SendShootInput(
                manager,
                "player-1",
                17,
                new Vector3(3f, 0f, 4f),
                "enemy-1");

            Assert.That(reliableTransport.SentMessages.Count, Is.EqualTo(1));
            Assert.That(syncTransport.SentMessages.Count, Is.EqualTo(0));

            var envelope = Envelope.Parser.ParseFrom(reliableTransport.SentMessages[0]);
            var shootInput = ShootInput.Parser.ParseFrom(envelope.Payload);
            Assert.That((MessageType)envelope.Type, Is.EqualTo(MessageType.ShootInput));
            Assert.That(shootInput.PlayerId, Is.EqualTo("player-1"));
            Assert.That(shootInput.Tick, Is.EqualTo(17));
            Assert.That(shootInput.DirX, Is.EqualTo(0.6f).Within(0.0001f));
            Assert.That(shootInput.DirY, Is.EqualTo(0.8f).Within(0.0001f));
            Assert.That(shootInput.TargetId, Is.EqualTo("enemy-1"));
        }

        [Test]
        public void SharedNetworkRuntime_CombatEventReceivePath_AppliesAuthoritativeDamageAndDeath()
        {
            var transport = new GameplayFlowFakeTransport();
            var runtime = new SharedNetworkRuntime(transport, new MainThreadNetworkDispatcher());
            var harness = new ClientGameplayTestHarness("player-1");
            harness.Register(runtime.MessageManager);

            transport.EmitReceive(
                GameplayFlowTestSupport.BuildEnvelope(
                    MessageType.PlayerState,
                    GameplayFlowTestSupport.CreatePlayerState("player-1", 10, Vector3.zero, hp: 100)),
                Sender);
            transport.EmitReceive(
                GameplayFlowTestSupport.BuildEnvelope(
                    MessageType.CombatEvent,
                    new CombatEvent
                    {
                        Tick = 11,
                        EventType = CombatEventType.DamageApplied,
                        AttackerId = "enemy-1",
                        TargetId = "player-1",
                        Damage = 35,
                        HitPosition = new global::Network.Defines.Vector3 { X = 2f, Y = 0f, Z = 1f }
                    }),
                Sender);
            transport.EmitReceive(
                GameplayFlowTestSupport.BuildEnvelope(
                    MessageType.CombatEvent,
                    new CombatEvent
                    {
                        Tick = 12,
                        EventType = CombatEventType.Death,
                        AttackerId = "enemy-1",
                        TargetId = "player-1"
                    }),
                Sender);

            runtime.DrainPendingMessagesAsync().GetAwaiter().GetResult();

            Assert.That(harness.TryGetState("player-1", out var snapshot), Is.True);
            Assert.That(snapshot.Hp, Is.EqualTo(0));
            Assert.That(harness.TryGetCombatPresentation("player-1", out var combatPresentation), Is.True);
            Assert.That(combatPresentation.HasLastEvent, Is.True);
            Assert.That(combatPresentation.LastEventType, Is.EqualTo(CombatEventType.Death));
            Assert.That(combatPresentation.IsDead, Is.True);
        }

        [Test]
        public void SharedNetworkRuntime_CombatEventReceivePath_RoutesShootRejectedToAttackerDiagnostics()
        {
            var transport = new GameplayFlowFakeTransport();
            var runtime = new SharedNetworkRuntime(transport, new MainThreadNetworkDispatcher());
            var harness = new ClientGameplayTestHarness("player-1");
            harness.Register(runtime.MessageManager);

            transport.EmitReceive(
                GameplayFlowTestSupport.BuildEnvelope(
                    MessageType.PlayerState,
                    GameplayFlowTestSupport.CreatePlayerState("player-1", 20, Vector3.zero, hp: 90)),
                Sender);
            transport.EmitReceive(
                GameplayFlowTestSupport.BuildEnvelope(
                    MessageType.CombatEvent,
                    new CombatEvent
                    {
                        Tick = 21,
                        EventType = CombatEventType.ShootRejected,
                        AttackerId = "player-1",
                        TargetId = "enemy-2"
                    }),
                Sender);

            runtime.DrainPendingMessagesAsync().GetAwaiter().GetResult();

            Assert.That(harness.TryGetState("player-1", out var snapshot), Is.True);
            Assert.That(snapshot.Hp, Is.EqualTo(90));
            Assert.That(harness.TryGetCombatPresentation("player-1", out var combatPresentation), Is.True);
            Assert.That(combatPresentation.LastEventType, Is.EqualTo(CombatEventType.ShootRejected));
            Assert.That(combatPresentation.LastDamage, Is.EqualTo(0));
            Assert.That(combatPresentation.IsDead, Is.False);
        }

        [Test]
        public void ClientGameplayHarness_RemotePlayerStateFlow_RejectsStaleSnapshots_AndUsesInterpolationOrLatestClamp()
        {
            var harness = new ClientGameplayTestHarness("local-player");

            var firstAccepted = harness.HandlePlayerState(
                GameplayFlowTestSupport.CreatePlayerState("remote-player", 10, new Vector3(0f, 0f, 0f), rotation: 0f),
                receivedAtSeconds: 0f);
            var secondAccepted = harness.HandlePlayerState(
                GameplayFlowTestSupport.CreatePlayerState("remote-player", 11, new Vector3(10f, 0f, 0f), rotation: 90f),
                receivedAtSeconds: 0.05f);
            var staleAccepted = harness.HandlePlayerState(
                GameplayFlowTestSupport.CreatePlayerState("remote-player", 9, new Vector3(99f, 0f, 0f), rotation: 180f),
                receivedAtSeconds: 0.06f);

            var interpolated = harness.SampleRemote("remote-player", 0.125f);
            var clamped = harness.SampleRemote("remote-player", 0.35f);

            Assert.That(firstAccepted, Is.True);
            Assert.That(secondAccepted, Is.True);
            Assert.That(staleAccepted, Is.False);
            Assert.That(harness.GetBufferedSnapshotCount("remote-player"), Is.EqualTo(1));
            Assert.That(harness.GetLatestBufferedTick("remote-player"), Is.EqualTo(11));
            Assert.That(interpolated.HasValue, Is.True);
            Assert.That(interpolated.UsedInterpolation, Is.True);
            Assert.That(interpolated.Position.x, Is.EqualTo(5f).Within(0.001f));
            Assert.That(interpolated.Rotation.eulerAngles.y, Is.EqualTo(45f).Within(0.01f));
            Assert.That(clamped.HasValue, Is.True);
            Assert.That(clamped.UsedInterpolation, Is.False);
            Assert.That(clamped.LatestSnapshot.Tick, Is.EqualTo(11));
            Assert.That(clamped.Position, Is.EqualTo(new Vector3(10f, 0f, 0f)));
        }
    }
}
