using System;
using System.Collections.Generic;
using Content.Server.Atmos;
using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.Components.Markers;
using Content.Server.GameObjects.Components.PDA;
using Content.Server.GameObjects.Components.TraitorDeathMatch;
using Content.Server.GameTicking.GameRules;
using Content.Server.Interfaces.Chat;
using Content.Server.Interfaces.GameTicking;
using Content.Server.Mobs;
using Content.Server.Mobs.Roles.Traitor;
using Content.Server.Players;
using Content.Shared;
using Content.Shared.Damage;
using Content.Shared.GameObjects.Components.Damage;
using Content.Shared.GameObjects.Components.Inventory;
using Content.Shared.GameObjects.Components.Mobs.State;
using Content.Shared.GameObjects.Components.PDA;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Random;

namespace Content.Server.GameTicking.GamePresets
{
    [GamePreset("traitordm", "traitordeathmatch")]
    public sealed class PresetTraitorDeathMatch : GamePreset
    {
        [Dependency] private readonly IConfigurationManager _cfg = default!;
        [Dependency] private readonly IEntityManager _entityManager = default!;
        [Dependency] private readonly IGameTicker _gameTicker = default!;
        [Dependency] private readonly IPlayerManager _playerManager = default!;
        [Dependency] private readonly IChatManager _chatManager = default!;
        [Dependency] private readonly IRobustRandom _robustRandom = default!;

        public string PDAPrototypeName => "CaptainPDA";
        public string BeltPrototypeName => "ClothingBeltJanitorFilled";
        public string BackpackPrototypeName => "ClothingBackpackFilled";

        private RuleMaxTimeRestart _restarter = default!;
        private bool _safeToEndRound = false;

        private Dictionary<UplinkAccount, string> _allOriginalNames = new();

        public override bool Start(IReadOnlyList<IPlayerSession> readyPlayers, bool force = false)
        {
            _gameTicker.AddGameRule<RuleTraitorDeathMatch>();
            _restarter = _gameTicker.AddGameRule<RuleMaxTimeRestart>();
            _restarter.RoundMaxTime = TimeSpan.FromMinutes(30);
            _restarter.RestartTimer();
            _safeToEndRound = true;
            return true;
        }

        public override void OnSpawnPlayerCompleted(IPlayerSession session, IEntity mob, bool lateJoin)
        {
            var startingBalance = _cfg.GetCVar(CCVars.TraitorDeathMatchStartingBalance);

            // Yup, they're a traitor
            var mind = session.Data.ContentData()?.Mind;
            if (mind == null)
            {
                Logger.ErrorS("preset", "Failed getting mind for TDM player.");
                return;
            }

            var traitorRole = new TraitorRole(mind);
            mind.AddRole(traitorRole);

            // Delete anything that may contain "dangerous" role-specific items.
            // (This includes the PDA, as everybody gets the captain PDA in this mode for true-all-access reasons.)
            if (mind.OwnedEntity != null && mind.OwnedEntity.TryGetComponent(out InventoryComponent? inventory))
            {
                var victimSlots = new[] {EquipmentSlotDefines.Slots.IDCARD, EquipmentSlotDefines.Slots.BELT, EquipmentSlotDefines.Slots.BACKPACK};
                foreach (var slot in victimSlots)
                {
                    if (inventory.TryGetSlotItem(slot, out ItemComponent? vItem))
                        vItem.Owner.Delete();
                }

                // Replace their items:

                //  pda
                var newPDA = _entityManager.SpawnEntity(PDAPrototypeName, mind.OwnedEntity.Transform.Coordinates);
                inventory.Equip(EquipmentSlotDefines.Slots.IDCARD, newPDA.GetComponent<ItemComponent>());

                //  belt
                var newTmp = _entityManager.SpawnEntity(BeltPrototypeName, mind.OwnedEntity.Transform.Coordinates);
                inventory.Equip(EquipmentSlotDefines.Slots.BELT, newTmp.GetComponent<ItemComponent>());

                //  backpack
                newTmp = _entityManager.SpawnEntity(BackpackPrototypeName, mind.OwnedEntity.Transform.Coordinates);
                inventory.Equip(EquipmentSlotDefines.Slots.BACKPACK, newTmp.GetComponent<ItemComponent>());

                // Like normal traitors, they need access to a traitor account.
                var uplinkAccount = new UplinkAccount(mind.OwnedEntity.Uid, startingBalance);
                var pdaComponent = newPDA.GetComponent<PDAComponent>();
                pdaComponent.InitUplinkAccount(uplinkAccount);
                _allOriginalNames[uplinkAccount] = mind.OwnedEntity.Name;

                // The PDA needs to be marked with the correct owner.
                pdaComponent.SetPDAOwner(mind.OwnedEntity.Name);
                newPDA.AddComponent<TraitorDeathMatchReliableOwnerTagComponent>().UserId = mind.UserId;
            }

            // Finally, it would be preferrable if they spawned as far away from other players as reasonably possible.
            if (mind.OwnedEntity != null && FindAnyIsolatedSpawnLocation(mind, out var bestTarget))
            {
                mind.OwnedEntity.Transform.Coordinates = bestTarget;
            }
            else
            {
                // The station is too drained of air to safely continue.
                if (_safeToEndRound)
                {
                    _chatManager.DispatchServerAnnouncement(Loc.GetString("The station is too unsafe to continue. You have one minute."));
                    _restarter.RoundMaxTime = TimeSpan.FromMinutes(1);
                    _restarter.RestartTimer();
                    _safeToEndRound = false;
                }
            }
        }

        // It would be nice if this function were moved to some generic helpers class.
        private bool FindAnyIsolatedSpawnLocation(Mind ignoreMe, out EntityCoordinates bestTarget)
        {
            // Collate people to avoid...
            var existingPlayerPoints = new List<EntityCoordinates>();
            foreach (var player in _playerManager.GetAllPlayers())
            {
                var avoidMeMind = player.Data.ContentData()?.Mind;
                if ((avoidMeMind == null) || (avoidMeMind == ignoreMe))
                    continue;
                var avoidMeEntity = avoidMeMind.OwnedEntity;
                if (avoidMeEntity == null)
                    continue;
                if (avoidMeEntity.TryGetComponent(out IMobStateComponent? mobState))
                {
                    // Does have mob state component; if critical or dead, they don't really matter for spawn checks
                    if (mobState.IsCritical() || mobState.IsDead())
                        continue;
                }
                else
                {
                    // Doesn't have mob state component. Assume something interesting is going on and don't count this as someone to avoid.
                    continue;
                }
                existingPlayerPoints.Add(avoidMeEntity.Transform.Coordinates);
            }

            // Iterate over each possible spawn point, comparing to the existing player points.
            // On failure, the returned target is the location that we're already at.
            var bestTargetDistanceFromNearest = -1.0f;
            // Need the random shuffle or it stuffs the first person into Atmospherics pretty reliably
            var ents = new List<IEntity>(_entityManager.GetEntities(new TypeEntityQuery(typeof(SpawnPointComponent))));
            _robustRandom.Shuffle(ents);
            var foundATarget = false;
            bestTarget = EntityCoordinates.Invalid;
            foreach (var entity in ents)
            {
                if (!entity.Transform.Coordinates.IsTileAirProbablySafe())
                    continue;
                var distanceFromNearest = float.PositiveInfinity;
                foreach (var existing in existingPlayerPoints)
                {
                    if (entity.Transform.Coordinates.TryDistance(_entityManager, existing, out var dist))
                        distanceFromNearest = Math.Min(distanceFromNearest, dist);
                }
                if (bestTargetDistanceFromNearest < distanceFromNearest)
                {
                    bestTarget = entity.Transform.Coordinates;
                    bestTargetDistanceFromNearest = distanceFromNearest;
                    foundATarget = true;
                }
            }
            return foundATarget;
        }

        public override bool OnGhostAttempt(Mind mind, bool canReturnGlobal)
        {
            var entity = mind.OwnedEntity;
            if ((entity != null) && (entity.TryGetComponent(out IMobStateComponent? mobState)))
            {
                if (mobState.IsCritical())
                {
                    // TODO: This is copy/pasted from ghost code. Really, IDamagableComponent needs a method to reliably kill the target.
                    if (entity.TryGetComponent(out IDamageableComponent? damageable))
                    {
                        //todo: what if they dont breathe lol
                        damageable.ChangeDamage(DamageType.Asphyxiation, 100, true);
                    }
                }
                else if (!mobState.IsDead())
                {
                    if (entity.HasComponent<HandsComponent>())
                    {
                        return false;
                    }
                }
            }
            var session = mind.Session;
            if (session == null)
                return false;
            _gameTicker.Respawn(session);
            return true;
        }

        public override string GetRoundEndDescription()
        {
            var lines = new List<string>();
            lines.Add("The PDAs recovered afterwards...");
            foreach (var entity in _entityManager.GetEntities(new TypeEntityQuery(typeof(PDAComponent))))
            {
                var pda = entity.GetComponent<PDAComponent>();
                var uplink = pda.SyndicateUplinkAccount;
                if ((uplink != null) && _allOriginalNames.ContainsKey(uplink))
                {
                    lines.Add(Loc.GetString("{0}'s PDA, with {1} TC", _allOriginalNames[uplink], uplink.Balance));
                }
            }
            return string.Join('\n', lines);
        }

        public override string ModeTitle => "Traitor Deathmatch";
        public override string Description => Loc.GetString("Everyone's a traitor. Everyone wants each other dead.");
    }
}
