using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Cirno.Scripts.Activables;
using Cirno.Scripts.Actors;
using Cirno.Scripts.Components.FSM.Enemy;
using Cirno.Scripts.Enums;
using Cirno.Scripts.Interactables;
using Cirno.Scripts.Resources;
using Cirno.Scripts.Resources.Loot;
using Cirno.Scripts.Resources.Roguelite;
using Cirno.Scripts.Utils;
using Godot;
using Godot.Collections;
using Array = Godot.Collections.Array;

namespace Cirno.Scripts.Controllers;

[Tool]
public partial class RogueliteRoom : Node2D
{
    [Export] public RogueliteRoomResource RoomResource { get; set; }

    [Export] public Array<Node2D> RoomClearActivation { get; set; }

    public RogueliteMapTheme MapTheme { get; set; }

    public Vector2I GridPosition { get; set; } // Set by dungeon manager

    public Vector2I BottomLeft => GridPosition + new Vector2I(0, RoomResource.Size.Y - 1);

    //private Vector2 BaseRoomSize => new Vector2(320, 160);
    private Vector2 BaseRoomSize => MapTheme.TileSize * MapTheme.RoomSizeInTiles;

    public Vector2 RoomSize => BaseRoomSize * RoomResource.Size;

    [Signal]
    public delegate void RoomClearedEventHandler();

    public Vector2I RandomBottomExit()
    {
        return BottomLeft + new Vector2I(GD.RandRange(0, RoomResource.Size.X - 1), 0);
    }

    public Vector2I RandomExit(Direction direction)
    {
        return direction switch
        {
            Direction.Up => GridPosition + new Vector2I(GD.RandRange(0, RoomResource.Size.X - 1), 0),
            Direction.Down => BottomLeft + new Vector2I(GD.RandRange(0, RoomResource.Size.X - 1), 0),
            Direction.Left => GridPosition + new Vector2I(0, GD.RandRange(0, RoomResource.Size.Y - 1)),
            Direction.Right => GridPosition + new Vector2I(RoomResource.Size.X - 1, 0) +
                               new Vector2I(0, GD.RandRange(0, RoomResource.Size.Y - 1)),
            _ => throw new ArgumentOutOfRangeException(nameof(direction), direction, null)
        };
    }

    private static readonly Godot.Collections.Dictionary<string, Vector2I> DirectionMap = new()
    {
        { "North", new Vector2I(0, -1) },
        { "South", new Vector2I(0, 1) },
        { "East", new Vector2I(1, 0) },
        { "West", new Vector2I(-1, 0) },
    };

    private List<Door> _doors = [];
    private List<RoomConnection> _connections = [];
    private List<EnemyFSMProxy> _enemies = [];

    public List<TeleporterMarker> Teleporters { get; private set; } = [];

    private Array<EnemyResource> SpawnableEnemies => RoomResource.SpawnableEnemies;

    private BlackCover _shroud;
    private bool _firstEnter = true;

    public RogueliteRoom Spawn()
    {
        //SpawnEnemies();
        SpawnFeatures();
        SpawnFixedWeapons();
        SpawnShroud();
        //HandleDoors(connectionChecker);
        //AddDebugLabel();
        return this;
    }

    private void AddDebugLabel()
    {
        for (int i = 0; i < RoomResource.Size.X; i++)
        {
            for (int j = 0; j < RoomResource.Size.Y; j++)
            {
                var label = new Label();
                label.Text = $"{GridPosition + new Vector2I(i, j)}";
                label.ZIndex = 11;
                label.Position = (new Vector2(i, j) * new Vector2(320, 160)) + new Vector2(160, 80);
                this.AddChild(label);
            }
        }
    }

    private List<DoorMarker> GenerateDoors()
    {
        List<DoorMarker> doors = [];

        var doorsContainer = new Node2D();
        this.AddChild(doorsContainer);
        doorsContainer.Name = "Doors";

        // North
        for (int i = 0; i < RoomResource.Size.X; i++)
        {
            if (RoomResource.HasDoors(DoorDirections.North))
            {
                doors.Add(MakeDoorMarker(DoorDirections.North, i));
            }

            if (RoomResource.HasDoors(DoorDirections.South))
            {
                doors.Add(MakeDoorMarker(DoorDirections.South, i));
            }
        }

        for (int j = 0; j < RoomResource.Size.Y; j++)
        {
            doors.Add(MakeDoorMarker(DoorDirections.East, j));
            doors.Add(MakeDoorMarker(DoorDirections.West, j));
        }

        foreach (var door in doors)
        {
            doorsContainer.AddChild(door);
        }

        return doors;
    }

    private DoorMarker MakeDoorMarker(DoorDirections direction, int wallIndex)
    {
        var doorMarker = new DoorMarker();
        doorMarker.Direction = direction;
        doorMarker.WallIndex = wallIndex;

        doorMarker.Position = GetDoorPosition(direction, wallIndex);

        return doorMarker;
    }

    public void HandleDoors(Func<Vector2I, Vector2I, RoomConnection> connectionChecker)
    {
        var doors = GenerateDoors();

        foreach (DoorMarker marker in doors)
        {
            var baseDir = marker.GetWorldDirection();

            // WallIndex determines the offset *along* the edge of the room
            Vector2I offset = marker.Direction switch
            {
                DoorDirections.North => new Vector2I(marker.WallIndex, 0),
                DoorDirections.South => new Vector2I(marker.WallIndex, RoomResource.Size.Y - 1),
                DoorDirections.East => new Vector2I(RoomResource.Size.X - 1, marker.WallIndex),
                DoorDirections.West => new Vector2I(0, marker.WallIndex),
                _ => Vector2I.Zero
            };

            // Combine GridPosition + offset to locate where this door aligns
            Vector2I doorEdge = GridPosition + offset;
            Vector2I neighborPos = doorEdge + baseDir;

            var connection = connectionChecker.Invoke(doorEdge, neighborPos);

            var connected = connection is not null;
            if (connected)
            {
                var door = this.CreateChildOf<Door>(marker, marker.Direction switch
                {
                    DoorDirections.North => MapTheme.HorizontalDoorPrefab,
                    DoorDirections.South => MapTheme.HorizontalDoorPrefab,
                    DoorDirections.East => MapTheme.VerticalDoorPrefab,
                    DoorDirections.West => MapTheme.VerticalDoorPrefab,
                    _ => throw new ArgumentOutOfRangeException()
                }, marker.GlobalPosition);

                door.Close();

                // door.State = DoorState.Closed;

                _doors.Add(door);

                if (doorEdge == connection.From)
                {
                    connection.FromDoor = door;

                    // Spawn lock if locked
                    if (connection.IsLocked)
                    {
                        var doorLock = door.CreateChild<RogueliteDoorLock>(MapTheme.DoorLockPrefab);

                        doorLock.Connection = connection;

                        doorLock.ZIndex += 2;

                        //doorLock.Targets.Add(door);
                        //doorLock.Targets.Add(connection.ToDoor);
                        doorLock.ActivationType = ActivationType.Open;
                    }
                }
                else if (doorEdge == connection.To)
                {
                    connection.ToDoor = door;
                }
                else
                {
                    GD.Print(
                        $"Door {door} connection was full: {connection.From} {connection.FromDoor} to {connection.To} {connection.ToDoor}");
                }

                _connections.Add(connection);
            }
            else
            {
                // Move marker based on direction
                var newMarkerPosition = marker.Direction switch
                {
                    DoorDirections.East => marker.GlobalPosition + new Vector2(-4, 0),
                    DoorDirections.West => marker.GlobalPosition + new Vector2(-12, 0),
                    DoorDirections.North => marker.GlobalPosition + new Vector2(0, 0),
                    DoorDirections.South => marker.GlobalPosition + new Vector2(0, -2),
                    _ => marker.GlobalPosition
                };

                marker.GlobalPosition = newMarkerPosition;

                var wall = this.CreateChildOf<Node2D>(marker, marker.Direction switch
                {
                    DoorDirections.North => MapTheme.HorizontalNorthWallPrefab,
                    DoorDirections.South => MapTheme.HorizontalSouthWallPrefab,
                    DoorDirections.East => MapTheme.VerticalWallPrefab,
                    DoorDirections.West => MapTheme.VerticalWallPrefab,
                    _ => throw new ArgumentOutOfRangeException()
                }, marker.GlobalPosition);
            }
        }
    }

    private void SpawnEnemies()
    {
        if (SpawnableEnemies is null || !SpawnableEnemies.Any()) return;

        var enemySpawnerMarkers = this.GetNode("EnemySpawners").GetChildren();

        foreach (var marker in enemySpawnerMarkers)
        {
            if (marker is not RogueliteEnemySpawner spawner) continue;

            var spawnedEnemy = spawner.Spawn(MapTheme);

            _enemies.Add(spawnedEnemy);

            spawnedEnemy.Death += SpawnedEnemyOnDeath;
        }
    }

    private void SpawnFixedWeapons()
    {
        var markersContainer = this.GetNodeOrNull("Treasures");
        if (markersContainer == null) return;

        var markerNodes = markersContainer.GetChildren();

        var itemsPool = MapTheme.WeaponsLootTable.Items.ToList().Shuffle().ToList();

        var playerItems = InventoryManager.Instance.Items;

        foreach (var itemContainer in playerItems)
        {
            if (itemsPool.Contains(itemContainer.Item))
            {
                itemsPool.Remove(itemContainer.Item);
            }
        }

        if (itemsPool.Count == 0)
        {
            GD.Print("No items to spawn in the item room found");
            return;
        }

        Queue<LootItem> spawnQueue = new();
        foreach (var item in itemsPool)
        {
            spawnQueue.Enqueue(item);
        }

        foreach (var markerNode in markerNodes)
        {
            if (markerNode is not Marker2D marker)
            {
                continue;
            }

            var itemFound = spawnQueue.TryDequeue(out var item);

            if (!itemFound) return;

            GD.Print($"Spawning {item.ItemKey} in treasure spot");

            var dropInstance = item.Spawn(marker);
            
            // Spawn
            // var dropScene = GD.Load<PackedScene>(item.DropScenePath);
            // var dropInstance = marker.CreateChild<Node2D>(dropScene);
        }
    }

    private void SpawnFeatures()
    {
        // Get feature markers
        // Roll for chances
        // Spawn the objects

        var markersContainer = this.GetNodeOrNull("Features");
        if (markersContainer is null) return;

        var markerNodes = markersContainer.GetChildren();

        foreach (var markerNode in markerNodes)
        {
            if (markerNode is not Marker2D marker)
            {
                continue;
            }

            if (markerNode is ChestMarker chestMarker)
            {
                double roll = GD.RandRange(0d, 100d);
                double chance = chestMarker.OverrideChance ? chestMarker.SpawnChance : MapTheme.ChestChance;
                if (roll <= chance)
                {
                    var hasLoot = MapTheme.ChestLootQueue.TryDequeue(out var loot);
                    if (!hasLoot)
                    {
                        GD.Print("Ran out of loot to spawn");
                        continue;
                    }

                    var chest = marker.CreateChild<Chest>(MapTheme.ChestPrefab);

                    chest.LootTable.Add(loot);
                }
            }
            else if (markerNode is TeleporterMarker teleporterMarker)
            {
                var tp = teleporterMarker.Spawn(MapTheme);

                MapTheme.TeleportersList.Add(tp);
                Teleporters.Add(tp);
            }
        }
    }

    private void SpawnedEnemyOnDeath(EnemyFSMProxy enemy)
    {
        enemy.Death -= SpawnedEnemyOnDeath;
        _enemies.Remove(enemy);

        if (_enemies.Count == 0)
        {
            OpenDoors();
            EnableLevelExitTeleporter();
            EmitSignalRoomCleared();
        }
    }

    private void EnableLevelExitTeleporter()
    {
        var teleporter = Teleporters.FirstOrDefault(x => x.Type is TeleporterMarkerType.NextLevel);
        
        if (teleporter is null) return;

        teleporter.SpawnedTeleporter.IsEnabled = true;
        teleporter.SpawnedTeleporter.Invisible = false;
        teleporter.SpawnedTeleporter.Show();
    }

    public void OpenDoors()
    {
        foreach (var connection in _connections)
        {
            if (!connection.IsLocked)
            {
                connection.FromDoor?.Open();
            }

            if (!connection.IsLocked)
            {
                connection.ToDoor?.Open();
            }
        }
    }

    public void CloseDoors()
    {
        foreach (var connection in _connections)
        {
            if (!connection.IsLocked)
            {
                connection.FromDoor?.Close();
            }

            if (!connection.IsLocked)
            {
                connection.ToDoor?.Close();
            }
        }
    }

    private void OnRoomEntered(Area2D area)
    {
        if (area is not InteractionController player) return;

        if (_firstEnter)
        {
            SpawnEnemies();
            _firstEnter = false;
            _shroud?.Activate(ActivationType.Disable);

            _ = AlarmTriggerAsync(area.GlobalPosition, 1f);
        }

        // This might cause problems later if I delay enemy spawns
        if (_enemies.Count <= 0)
        {
            OpenDoors();
            // TODO: Just for testing
            EnableLevelExitTeleporter();
        }
        else
        {
            CloseDoors();
        }
    }

    private async Task AlarmTriggerAsync(Vector2 position, float secondsDelay)
    {
        await Task.Delay((int)(secondsDelay * 1000));
        AlarmManager.Instance.SoundSilentAlarm(position);
    }

    private void OnRoomExited(Area2D area)
    {
        if (area is not InteractionController player) return;
    }

    public override string ToString()
    {
        return $"{GridPosition} {RoomResource}";
    }

    public Vector2 GetDoorPosition(DoorDirections direction, int wallIndex)
    {
        return direction switch
        {
            DoorDirections.North => new Vector2((BaseRoomSize.X / 2) + (BaseRoomSize.X * wallIndex), 32),
            DoorDirections.South => new Vector2((BaseRoomSize.X / 2) + (BaseRoomSize.X * wallIndex),
                ((BaseRoomSize.Y) * RoomResource.Size.Y) + 2),
            DoorDirections.East => new Vector2((BaseRoomSize.X * RoomResource.Size.X) - 12,
                (BaseRoomSize.Y / 2) + (BaseRoomSize.Y * wallIndex) - 8),
            DoorDirections.West => new Vector2(12, (BaseRoomSize.Y / 2) + (BaseRoomSize.Y * wallIndex) - 8),
            _ => Vector2.Zero
        };
    }

    // [ExportToolButton("Arrange Doors")] public Callable ArrangeDoorsButton => Callable.From(ArrangeDoors);

    public void ArrangeDoors()
    {
        var doorNode = this.GetNode("Doors");

        var doors = doorNode.GetChildren();

        foreach (var node in doors)
        {
            if (node is DoorMarker doorMarker)
            {
                GD.Print($"{doorMarker.Name} {doorMarker.Direction} {doorMarker.WallIndex}");

                //var baseGridSize = new Vector2(320, 160);

                Vector2 doorPosition = doorMarker.Direction switch
                {
                    DoorDirections.North => new Vector2((BaseRoomSize.X / 2) + (BaseRoomSize.X * doorMarker.WallIndex),
                        32),
                    DoorDirections.South => new Vector2((BaseRoomSize.X / 2) + (BaseRoomSize.X * doorMarker.WallIndex),
                        ((BaseRoomSize.Y) * RoomResource.Size.Y) + 2),
                    DoorDirections.East => doorMarker.Position,
                    DoorDirections.West => doorMarker.Position,
                    _ => doorMarker.Position
                };

                doorMarker.Position = doorPosition;
            }
            else
            {
                GD.Print($"Node was something else: {node}");
            }
        }
    }

    public void SpawnShroud()
    {
        if (!RoomResource.StartShrouded) return;

        var shroud = this.CreateChild<BlackCover>(MapTheme.ShroudPrefab);

        shroud.Position = new Vector2(RoomSize.X / 2, RoomSize.Y / 2);
        shroud.Scale = RoomSize;

        _shroud = shroud;
    }
}