using System;
using System.Collections.Generic;
using AceFieldNewHorizon.Scripts.Tiles;
using Godot;
using System.Linq;

namespace AceFieldNewHorizon.Scripts.System;

public partial class PlacementManager : Node2D
{
    public GridManager Grid { get; private set; }
    public ResourceManager Inventory { get; private set; }
    public BuildingRegistry Registry { get; private set; }

    [Export] public int MaxConcurrentBuilds { get; set; } = 6; // Make it adjustable in editor
    [Export] public bool Enabled { get; set; } = true;
    [Export] public StringName ToggleBuildAction { get; set; } = "toggle_build";

    private static readonly List<string> BuildableTiles = ["wall", "miner", "turret"];
    private readonly Dictionary<Node2D, BuildTask> _buildTasks = new();
    private AudioStreamPlayer _completionSound;
    private AudioStreamPlayer _buildingSound;
    private AudioStreamPlayer _canceledSound;
    private AudioStreamPlayer _cannotDeploySound;
    private AudioStreamPlayer _notReadySound;
    private AudioStreamPlayer _insufficientFundsSound;
    private Node2D _currentGhost; // Keep track of the current ghost building

    public override void _Ready()
    {
        base._Ready();

        Grid = DependencyInjection.Container.GetInstance<GridManager>();
        Inventory = DependencyInjection.Container.GetInstance<ResourceManager>();
        Registry = DependencyInjection.Container.GetInstance<BuildingRegistry>();

        // Setup completion sound
        _completionSound = CreateAudioPlayer("res://Sounds/Events/ConstructionComplete.wav");
        _buildingSound = CreateAudioPlayer("res://Sounds/Events/Building.wav");
        _canceledSound = CreateAudioPlayer("res://Sounds/Events/Canceled.wav");
        _cannotDeploySound = CreateAudioPlayer("res://Sounds/Events/CannotDeployHere.wav");
        _notReadySound = CreateAudioPlayer("res://Sounds/Events/NotReady.wav");
        _insufficientFundsSound = CreateAudioPlayer("res://Sounds/Events/InsufficientFunds.wav");
    }

    private AudioStreamPlayer CreateAudioPlayer(string path)
    {
        var player = new AudioStreamPlayer();
        AddChild(player);
        var sound = GD.Load<AudioStream>(path);
        if (sound != null)
        {
            player.Stream = sound;
        }

        return player;
    }

    private void OnBuildCompleted()
    {
        // Remove all completed builds
        var completed = _buildTasks.Where(kvp => kvp.Value.IsCompleted)
            .Select(kvp => kvp.Key)
            .ToList();
        foreach (var key in completed)
        {
            _buildTasks.Remove(key);
        }

        // If no builds left, play the completion sound
        if (_buildTasks.Count == 0)
        {
            _completionSound.Play();
        }
    }

    // Call this when starting a new build
    private bool CanStartNewBuild()
    {
        // Remove completed builds
        var completed = _buildTasks.Where(kvp => kvp.Value.IsCompleted)
            .Select(kvp => kvp.Key)
            .ToList();
        foreach (var key in completed)
        {
            _buildTasks.Remove(key);
        }

        return _buildTasks.Count < MaxConcurrentBuilds;
    }

    private class BuildTask(Action onCompleted) : IDisposable
    {
        public bool IsCompleted { get; private set; }
        public bool WasCancelled { get; private set; }

        public void Complete(bool wasCancelled = false)
        {
            if (IsCompleted) return;

            IsCompleted = true;
            WasCancelled = wasCancelled;
            onCompleted?.Invoke();
        }

        public void Dispose()
        {
            Complete(true); // Mark as cancelled if disposed before completion
        }
    }

    private string _currentBuildingId = "wall";
    private Vector2I _hoveredCell;
    private BaseTile _ghostBuilding;
    private float _currentRotation;
    private Vector2I _currentBuildingSize = Vector2I.One;

    public void SetCurrentBuilding(string buildingId)
    {
        _currentBuildingId = buildingId;
        var buildingData = Registry.GetBuilding(buildingId);
        if (buildingData != null)
        {
            _currentBuildingSize = buildingData.Size;
            // Reset rotation to nearest allowed rotation
            if (!buildingData.IsRotationAllowed(_currentRotation))
            {
                _currentRotation = (int)buildingData.AllowedRotations[0] * 90f;
            }
        }

        // Replace ghost immediately
        if (_ghostBuilding != null)
        {
            _ghostBuilding.QueueFree();
            _ghostBuilding = null;
        }
    }

    private void RotateGhost(bool reverse = false)
    {
        if (_ghostBuilding == null) return;

        var buildingData = Registry.GetBuilding(_currentBuildingId);
        if (buildingData == null) return;

        // Calculate next rotation
        var currentDirection = (RotationDirection)((Mathf.RoundToInt(_currentRotation / 90f) % 4 + 4) % 4);
        var currentIndex = Array.IndexOf(buildingData.AllowedRotations.ToArray(), currentDirection);

        if (reverse)
            currentIndex = (currentIndex - 1 + buildingData.AllowedRotations.Length) %
                           buildingData.AllowedRotations.Length;
        else
            currentIndex = (currentIndex + 1) % buildingData.AllowedRotations.Length;

        _currentRotation = (int)buildingData.AllowedRotations[currentIndex] * 90f;
        _ghostBuilding.RotationDegrees = _currentRotation;

        // Update ghost position to keep the same cell under cursor
        UpdateGhostPosition();
    }

    private void UpdateGhostPosition()
    {
        if (_ghostBuilding == null) return;

        var buildingData = Registry.GetBuilding(_currentBuildingId);
        if (buildingData == null) return;

        var rotatedSize = buildingData.GetRotatedSize(_currentRotation);
        var offset = GridUtils.GetCenterOffset(rotatedSize, _currentRotation);
        _ghostBuilding.Position = GridUtils.GridToWorld(_hoveredCell) + offset;

        // Update occupied cells
        GridUtils.GetOccupiedCells(_hoveredCell, rotatedSize, _currentRotation);
    }

    public override void _Process(double delta)
    {
        if (!Enabled) return;

        // Snap mouse to grid
        var mousePos = GetGlobalMousePosition();
        var newHoveredCell = GridUtils.WorldToGrid(mousePos);

        // Only update if cell changed
        if (newHoveredCell != _hoveredCell)
        {
            _hoveredCell = newHoveredCell;
            UpdateGhostPosition();
        }

        if (Input.IsActionJustPressed("rotate_tile_reverse"))
            RotateGhost(reverse: true);
        else if (Input.IsActionJustPressed("rotate_tile"))
            RotateGhost();

        if (_ghostBuilding == null)
        {
            var building = Registry.GetBuilding(_currentBuildingId);
            if (building == null) return;
            var scene = building.Scene;

            _ghostBuilding = (BaseTile)scene.Instantiate();
            _ghostBuilding.SetGhostMode(true);
            _ghostBuilding.RotationDegrees = _currentRotation;
            _ghostBuilding.ZAsRelative = false;
            _ghostBuilding.ZIndex = (int)building.Layer;
            UpdateGhostPosition();
            AddChild(_ghostBuilding);
        }

        var canPlace = CanPlaceBuilding();
        _ghostBuilding.SetGhostMode(canPlace);

        // Left click to place
        if (Input.IsActionPressed("build_tile"))
        {
            var building = Registry.GetBuilding(_currentBuildingId);
            if (building == null) return;

            if (!CanStartNewBuild())
            {
                _notReadySound.Play();
                return;
            }

            // First check if area is free
            if (!IsAreaFree(_hoveredCell, building.Size, _currentRotation, building.Layer))
            {
                // Check if the area is occupied by under-construction tiles
                var occupiedCells = GridUtils.GetOccupiedCells(_hoveredCell, building.Size, _currentRotation);
                var isUnderConstruction = occupiedCells.Any(cell =>
                    Grid.GetTileAtCell(cell, building.Layer) is BaseTile { IsConstructing: true });

                if (!isUnderConstruction)
                    _cannotDeploySound.Play();

                return;
            }
            
            // Consume resources first
            if (!ConsumeBuildingResources(_currentBuildingId))
            {
                _insufficientFundsSound.Play();
                return;
            }

            // Create the building instance
            var scene = building.Scene;
            var buildingInstance = (BaseTile)scene.Instantiate();
            buildingInstance.RotationDegrees = _currentRotation;
            buildingInstance.ZIndex = (int)building.Layer;
            buildingInstance.Position = _ghostBuilding.Position;
            AddChild(buildingInstance);

            // If we get here, area is free, so we can safely occupy it
            Grid.OccupyArea(_hoveredCell, buildingInstance, building.Size, _currentRotation, building.Layer);

            if (building.BuildTime > 0f)
            {
                var wasQueueEmpty = _buildTasks.Count == 0;
                var buildTask = new BuildTask(OnBuildCompleted);
                _buildTasks[buildingInstance] = buildTask;

                // Play building sound only when adding to an empty queue
                if (wasQueueEmpty)
                    _buildingSound.Play();

                buildingInstance.StartConstruction(building.BuildTime, () =>
                {
                    // On construction complete
                    if (_buildTasks.TryGetValue(buildingInstance, out var task))
                    {
                        if (task.WasCancelled)
                        {
                            RefundBuildingResources(_currentBuildingId);
                            Grid.FreeArea(_hoveredCell, building.Size, _currentRotation, building.Layer);
                            buildingInstance.QueueFree();
                        }

                        task.Complete();
                        _buildTasks.Remove(buildingInstance);
                    }
                });
            }
        }

        if (Input.IsActionPressed("destroy_tile") &&
            !Grid.IsAreaFree(_hoveredCell, Vector2I.One, 0f))
        {
            // Right click to destroy from current layer
            var building = Grid.GetTileAtCell(_hoveredCell);
            if (building == null) return;

            // Find all cells occupied by this building
            var buildingInfo = Grid.GetBuildingInfoAtCell(_hoveredCell, GridLayer.Building);
            if (buildingInfo == null) return;

            // Check if this building is in the build tasks (under construction)
            if (_buildTasks.TryGetValue(building, out var buildTask))
            {
                var buildingTile = building as BaseTile;
                // Cancel the build task
                buildTask.Complete(true); // Mark as cancelled
                _buildTasks.Remove(building);
                _canceledSound.Play();
                if (buildingTile == null) return;

                // Refund resources for canceled build
                var buildingData = Registry.GetBuilding(buildingTile.TileId);
                if (buildingData != null)
                    RefundBuildingResources(buildingTile.TileId);
            }

            // Clean up the building and grid
            building.QueueFree();
            Grid.FreeArea(buildingInfo.Value.Position, buildingInfo.Value.Size, buildingInfo.Value.Rotation);
        }

        if (Input.IsActionJustPressed("switch_tile"))
        {
            var currentIdx = BuildableTiles.IndexOf(_currentBuildingId);
            var nextIdx = (currentIdx + 1) % BuildableTiles.Count;
            SetCurrentBuilding(BuildableTiles[nextIdx]);
        }
    }

    public override void _Input(InputEvent @event)
    {
        if (@event.IsActionPressed(ToggleBuildAction))
        {
            Enabled = !Enabled;

            // Hide ghost building when disabling
            if (!Enabled && _ghostBuilding != null && _ghostBuilding.IsInsideTree())
            {
                _ghostBuilding.QueueFree();
                _ghostBuilding = null;
            }
        }
    }

    public override void _ExitTree()
    {
        base._ExitTree();
        _buildTasks.Clear();
    }

    private bool CanAffordBuilding(string buildingId)
    {
        if (Inventory == null) return false;

        var buildingData = Registry.GetBuilding(buildingId);
        if (buildingData == null) return false;

        // Check if we have enough of each required resource
        foreach (var (resourceId, amount) in buildingData.Cost)
            if (!Inventory.HasResource(resourceId, amount))
                return false;
        return true;
    }

    private bool ConsumeBuildingResources(string buildingId)
    {
        if (Inventory == null) return false;

        var buildingData = Registry.GetBuilding(buildingId);
        if (buildingData == null) return false;

        // First verify we can afford it
        if (!CanAffordBuilding(buildingId)) return false;

        // Then consume each resource
        foreach (var (resourceId, amount) in buildingData.Cost)
            Inventory.RemoveResource(resourceId, amount);

        return true;
    }

    private void RefundBuildingResources(string buildingId)
    {
        if (Inventory == null) return;

        var buildingData = Registry.GetBuilding(buildingId);
        if (buildingData == null) return;

        // Refund each resource
        foreach (var (resourceId, amount) in buildingData.Cost)
        {
            Inventory.AddResource(resourceId, amount);
        }
    }

    private bool CanPlaceBuilding()
    {
        var buildingData = Registry.GetBuilding(_currentBuildingId);
        if (buildingData == null) return false;

        // Check if we can afford the building
        if (!CanAffordBuilding(_currentBuildingId)) return false;

        // Check if area is free
        var rotatedSize = buildingData.GetRotatedSize(_currentRotation);
        return !Grid.IsAreaOccupied(_hoveredCell, rotatedSize, _currentRotation, GetBlockingLayers(buildingData.Layer));
    }

    private GridLayer[] GetBlockingLayers(GridLayer layer)
    {
        return layer switch
        {
            GridLayer.Ground => [GridLayer.Ground],
            GridLayer.Building => [GridLayer.Building],
            GridLayer.Decoration => [GridLayer.Decoration],
            _ => []
        };
    }

    private bool IsAreaFree(Vector2I topLeft, Vector2I size, float rotation, GridLayer layer)
    {
        return !Grid.IsAreaOccupied(topLeft, size, rotation, GetBlockingLayers(layer));
    }
}

public static class GridManagerExtensions
{
    public static (Vector2I Position, Vector2I Size, float Rotation)? GetBuildingInfoAtCell(this GridManager grid,
        Vector2I cell, GridLayer layer)
    {
        if (grid.GetTileAtCell(cell, layer) is not { } building) return null;
        // Find the top-left position of the building
        for (var x = 0; x < 100; x++) // Arbitrary max size
        {
            for (var y = 0; y < 100; y++)
            {
                var checkCell = new Vector2I(cell.X - x, cell.Y - y);
                if (grid.GetTileAtCell(checkCell, layer) != building) continue;
                // Found the top-left corner, now find the size
                var size = Vector2I.One;
                // Search right
                while (grid.GetTileAtCell(new Vector2I(checkCell.X + size.X, checkCell.Y), layer) ==
                       building)
                    size.X++;
                // Search down
                while (grid.GetTileAtCell(new Vector2I(checkCell.X, checkCell.Y + size.Y), layer) ==
                       building)
                    size.Y++;

                // Get rotation from the first cell
                var rotation = 0f; // You'll need to store rotation in Grid to make this work
                return (checkCell, size, rotation);
            }
        }

        return null;
    }
}