using System;
using System.Collections.Concurrent;
using Godot;
using System.Collections.Generic;
using System.Threading.Tasks;
using AceFieldNewHorizon.Scripts.Entities;
using AceFieldNewHorizon.Scripts.Tiles;

namespace AceFieldNewHorizon.Scripts.System;

public partial class NaturalResourceGenerator : Node2D
{
    public const string ChunkTrackerGroupName = "NrgTrackingTarget";

    public GridManager Grid { get; private set; }
    public BuildingRegistry Registry { get; private set; }

    [Export] public int ChunkSize = 16;
    [Export] public int LoadDistance = 2; // Number of chunks to load in each direction
    [Export] public float StoneDensity = 0.1f;
    [Export] public float IronDensity = 0.03f;
    [Export] public int MinStoneVeinSize = 1;
    [Export] public int MaxStoneVeinSize = 5;
    [Export] public int MinIronVeinSize = 1;
    [Export] public int MaxIronVeinSize = 3;
    [Export] public int Seed;

    [Export] public bool SpawnEnemyNest = true;
    [Export] public int MinDistanceFromOrigin = 20; // Minimum distance from world origin (0,0)
    [Export] public int MaxDistanceFromOrigin = 50; // Maximum distance from world origin

    private const string LogPrefix = "[NaturalGeneration]";

    private RandomNumberGenerator _rng;
    private readonly Dictionary<Vector2I, ChunkData> _loadedChunks = new();
    private Vector2I _lastPlayerChunk = new(-1000, -1000);

    private Player _player;

    private readonly ConcurrentQueue<Action> _mainThreadActions = new();
    private Task _generationTask;
    private bool _isRunning = true;

    public override void _Ready()
    {
        Grid = DependencyInjection.Container.GetInstance<GridManager>();
        Registry = DependencyInjection.Container.GetInstance<BuildingRegistry>();
        _rng = new RandomNumberGenerator();
        _rng.Seed = (ulong)(Seed != 0 ? Seed : (int)GD.Randi());
    
        // Test if building registry is assigned
        if (Registry == null)
        {
            GD.PrintErr($"{LogPrefix} BuildingRegistry is not assigned!");
            return;
        }
    
        // Test if enemy_portal is in the registry
        var testBuilding = Registry.GetBuilding("enemy_portal");
        if (testBuilding == null)
        {
            GD.PrintErr($"{LogPrefix} 'enemy_portal' is not found in BuildingRegistry!");
        }
        else
        {
            GD.Print($"{LogPrefix} Found enemy_portal in registry!");
        }
    
        GD.Print($"{LogPrefix} NaturalResourceGenerator ready, SpawnEnemyNest = {SpawnEnemyNest}");
        
        GD.Print($"{LogPrefix} Spawning the core reactor...");
        SpawnCoreReactor();
    
        if (SpawnEnemyNest)
        {
            GD.Print($"{LogPrefix} Attempting to spawn enemy nest...");
            SpawnRandomEnemyNest();
        }
    }

    public override void _Process(double delta)
    {
        // Process any pending main thread actions
        while (_mainThreadActions.TryDequeue(out var action))
        {
            action.Invoke();
        }

        // Rest of your existing _Process code
        _player = GetTree().GetFirstNodeInGroup(ChunkTrackerGroupName) as Player;
        if (_player != null)
        {
            UpdatePlayerPosition(_player.GlobalPosition);
        }
        else
        {
            GD.PrintErr($"{LogPrefix} Player not found in group: {ChunkTrackerGroupName}");
        }
    }

    private void UpdatePlayerPosition(Vector2 playerPosition)
    {
        var playerChunk = WorldToChunkCoords(playerPosition);

        if (playerChunk == _lastPlayerChunk) return;
        _lastPlayerChunk = playerChunk;

        // Start generation in background task if not already running
        if (_generationTask == null || _generationTask.IsCompleted)
        {
            _generationTask = Task.Run(() => GenerateChunksAroundPlayer(playerChunk));
        }
    }

    private void GenerateChunksAroundPlayer(Vector2I playerChunk)
    {
        var chunksToLoad = new List<Vector2I>();

        // Determine which chunks to load/unload
        for (int x = -LoadDistance; x <= LoadDistance; x++)
        {
            for (int y = -LoadDistance; y <= LoadDistance; y++)
            {
                var chunkPos = new Vector2I(playerChunk.X + x, playerChunk.Y + y);
                if (!_loadedChunks.ContainsKey(chunkPos))
                {
                    chunksToLoad.Add(chunkPos);
                }
            }
        }

        // Generate chunks in background
        foreach (var chunkPos in chunksToLoad)
        {
            if (!_isRunning) return;
            GenerateChunkInBackground(chunkPos);
        }
    }

    private void GenerateChunkInBackground(Vector2I chunkPos)
    {
        // Skip if chunk was loaded while we were waiting
        if (_loadedChunks.ContainsKey(chunkPos)) return;

        var chunkData = new ChunkData();
        var chunkWorldPos = ChunkToWorldCoords(chunkPos);

        // Generate ground tiles
        for (int x = 0; x < ChunkSize; x++)
        {
            for (int y = 0; y < ChunkSize; y++)
            {
                var cell = new Vector2I(chunkWorldPos.X + x, chunkWorldPos.Y + y);
                _mainThreadActions.Enqueue(() => PlaceTile("ground", cell));
            }
        }

        // Generate stone veins
        for (var x = 0; x < ChunkSize; x++)
        {
            for (var y = 0; y < ChunkSize; y++)
            {
                var cell = new Vector2I(chunkWorldPos.X + x, chunkWorldPos.Y + y);
                if (_rng.Randf() < StoneDensity)
                {
                    var veinSize = _rng.RandiRange(MinStoneVeinSize, MaxStoneVeinSize);
                    PlaceVeinInBackground(cell, "stone", veinSize, chunkData.StoneTiles);
                }
                else if (_rng.Randf() < IronDensity)
                {
                    var veinSize = _rng.RandiRange(MinIronVeinSize, MaxIronVeinSize);
                    PlaceVeinInBackground(cell, "ore_iron", veinSize, chunkData.IronTiles);
                }
            }
        }

        // Add chunk data to dictionary on main thread
        _mainThreadActions.Enqueue(() => { _loadedChunks[chunkPos] = chunkData; });
    }

    private void PlaceVeinInBackground(Vector2I startCell, string tileType, int maxSize, List<Vector2I> tileList)
    {
        var placed = new HashSet<Vector2I>();
        var queue = new Queue<Vector2I>();
        queue.Enqueue(startCell);

        int placedCount = 0;

        while (queue.Count > 0 && placedCount < maxSize && _isRunning)
        {
            var cell = queue.Dequeue();
            if (placed.Contains(cell)) continue;

            // Schedule tile placement on main thread
            _mainThreadActions.Enqueue(() =>
            {
                if (PlaceTile(tileType, cell))
                {
                    tileList.Add(cell);
                }
            });

            placed.Add(cell);
            placedCount++;

            // Add adjacent cells
            var directions = new[] { Vector2I.Up, Vector2I.Right, Vector2I.Down, Vector2I.Left };
            foreach (var dir in directions)
            {
                var newCell = cell + dir;
                if (!placed.Contains(newCell))
                {
                    queue.Enqueue(newCell);
                }
            }
        }
    }

    public override void _ExitTree()
    {
        // Clean up
        _isRunning = false;
        _generationTask?.Wait(); // Wait for current generation to finish
        base._ExitTree();
    }

    private void GenerateChunk(Vector2I chunkPos)
    {
        GD.Print($"{LogPrefix} Generating chunk at {chunkPos}");
        var chunkData = new ChunkData();
        var chunkWorldPos = ChunkToWorldCoords(chunkPos);

        // First, place ground tiles
        for (int x = 0; x < ChunkSize; x++)
        {
            for (int y = 0; y < ChunkSize; y++)
            {
                var cell = new Vector2I(chunkWorldPos.X + x, chunkWorldPos.Y + y);
                if (!PlaceTile("ground", cell))
                {
                    GD.PrintErr($"{LogPrefix} Failed to place ground at {cell}");
                }
            }
        }

        // Then generate stone veins
        var stoneVeins = 0;
        for (var x = 0; x < ChunkSize; x++)
        {
            for (var y = 0; y < ChunkSize; y++)
            {
                var cell = new Vector2I(chunkWorldPos.X + x, chunkWorldPos.Y + y);
                if (_rng.Randf() < StoneDensity)
                {
                    var veinSize = _rng.RandiRange(MinStoneVeinSize, MaxStoneVeinSize);
                    GD.Print($"{LogPrefix} Attempting to place stone vein at {cell} with size {veinSize}");
                    PlaceVein(cell, "stone", veinSize, chunkData.StoneTiles);
                    stoneVeins++;
                }
            }
        }

        GD.Print($"{LogPrefix} Placed {stoneVeins} stone veins in chunk {chunkPos}");

        // Generate iron veins within stone
        int ironVeins = 0;
        foreach (var stoneCell in new List<Vector2I>(chunkData.StoneTiles))
        {
            if (_rng.Randf() < IronDensity)
            {
                var veinSize = _rng.RandiRange(MinIronVeinSize, MaxIronVeinSize);
                GD.Print($"{LogPrefix} Attempting to place iron vein at {stoneCell} with size {veinSize}");
                PlaceVein(stoneCell, "ore_iron", veinSize, chunkData.IronTiles);
                ironVeins++;
            }
        }

        GD.Print($"{LogPrefix} Placed {ironVeins} iron veins in chunk {chunkPos}");

        _loadedChunks[chunkPos] = chunkData;
    }

    private void UnloadChunk(Vector2I chunkPos)
    {
        GD.Print($"{LogPrefix} Unloading chunk at {chunkPos}");
        if (!_loadedChunks.TryGetValue(chunkPos, out var chunkData)) return;

        // Remove all tiles in this chunk
        var chunkWorldPos = ChunkToWorldCoords(chunkPos);
        for (var x = 0; x < ChunkSize; x++)
        {
            for (var y = 0; y < ChunkSize; y++)
            {
                var cell = new Vector2I(chunkWorldPos.X + x, chunkWorldPos.Y + y);
                // Free a 1x1 area for each cell
                Grid.FreeArea(cell, Vector2I.One, 0f, GridLayer.Ground);
            }
        }

        _loadedChunks.Remove(chunkPos);
    }

    private Vector2I WorldToChunkCoords(Vector2 worldPos)
    {
        var cell = GridUtils.WorldToGrid(worldPos);
        return new Vector2I(
            (int)Mathf.Floor((float)cell.X / ChunkSize),
            (int)Mathf.Floor((float)cell.Y / ChunkSize)
        );
    }

    private Vector2I ChunkToWorldCoords(Vector2I chunkPos)
    {
        return new Vector2I(
            chunkPos.X * ChunkSize,
            chunkPos.Y * ChunkSize
        );
    }

    private bool IsInLoadedChunk(Vector2I cell)
    {
        // Check the chunk where the cell is located
        var chunkPos = new Vector2I(
            (int)Mathf.Floor((float)cell.X / ChunkSize),
            (int)Mathf.Floor((float)cell.Y / ChunkSize)
        );

        // Also check adjacent chunks since veins can cross chunk boundaries
        for (var dx = -1; dx <= 1; dx++)
        {
            for (var dy = -1; dy <= 1; dy++)
            {
                var checkPos = new Vector2I(chunkPos.X + dx, chunkPos.Y + dy);
                if (_loadedChunks.ContainsKey(checkPos))
                {
                    return true;
                }
            }
        }

        return false;
    }

    private void PlaceVein(Vector2I startCell, string tileType, int maxSize, List<Vector2I> tileList)
    {
        GD.Print($"{LogPrefix} Starting to place vein of type {tileType} at {startCell} with max size {maxSize}");

        var placed = new HashSet<Vector2I>();
        var queue = new Queue<Vector2I>();
        queue.Enqueue(startCell);

        int placedCount = 0;

        while (queue.Count > 0 && placedCount < maxSize)
        {
            var cell = queue.Dequeue();

            // Skip if already placed or out of bounds
            if (placed.Contains(cell))
            {
                GD.Print($"{LogPrefix} Skipping cell {cell} - already placed");
                continue;
            }

            if (!IsInLoadedChunk(cell))
            {
                GD.Print($"{LogPrefix} Skipping cell {cell} - out of bounds");
                continue;
            }

            // Try to place the tile
            if (PlaceTile(tileType, cell))
            {
                tileList.Add(cell);
                placed.Add(cell);
                placedCount++;
                // GD.Print($"{LogPrefix} Successfully placed {tileType} at {cell} ({placedCount}/{maxSize})");

                // Add adjacent cells to queue
                var directions = new[] { Vector2I.Up, Vector2I.Right, Vector2I.Down, Vector2I.Left };
                foreach (var dir in directions)
                {
                    var newCell = cell + dir;
                    if (!placed.Contains(newCell))
                    {
                        queue.Enqueue(newCell);
                    }
                }
            }
            else
            {
                GD.Print($"{LogPrefix} Failed to place {tileType} at {cell}");
            }
        }

        GD.Print($"{LogPrefix} Finished placing vein - placed {placedCount}/{maxSize} {tileType} tiles");
    }

    private void SpawnCoreReactor()
    {
        // Place the reactor tile at the center of the map
        PlaceTile("reactor", new Vector2I(0, 0));
    }

    private void SpawnRandomEnemyNest()
    {
        // Generate a random position within the specified distance from origin
        var angle = _rng.Randf() * Mathf.Pi * 2;
        var distance = _rng.RandfRange(MinDistanceFromOrigin, MaxDistanceFromOrigin);
        var offset = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * distance;
        var nestPosition = new Vector2I((int)offset.X, (int)offset.Y);
        
        // Try to find a valid position for the nest
        var attempts = 0;
        const int maxAttempts = 10;
        
        while (attempts < maxAttempts)
        {
            if (PlaceTile("enemy_portal", nestPosition))
            {
                GD.Print($"{LogPrefix} Placed enemy nest at {nestPosition}");
                return;
            }
            
            // Try a different position if placement failed
            angle = _rng.Randf() * Mathf.Pi * 2;
            distance = _rng.RandfRange(MinDistanceFromOrigin, MaxDistanceFromOrigin);
            offset = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * distance;
            nestPosition = new Vector2I((int)offset.X, (int)offset.Y);
            attempts++;
        }
        
        GD.PrintErr($"{LogPrefix} Failed to place enemy nest after {maxAttempts} attempts");
    }

    private bool PlaceTile(string tileType, Vector2I cell)
    {
        try
        {
            var building = Registry.GetBuilding(tileType);
            if (building == null)
            {
                GD.PrintErr($"{LogPrefix} Building type not found in registry: {tileType}");
                return false;
            }

            // Free area for ground layer if needed
            if (building.Layer == GridLayer.Ground)
                Grid.FreeArea(cell, building.Size, 0f, GridLayer.Ground);
            else
                GD.Print($"{LogPrefix} Attempting placing building tile {tileType} at {cell}.");

            var scene = building.Scene;
            if (scene == null)
            {
                GD.PrintErr($"{LogPrefix} Scene is null for building type: {tileType}");
                return false;
            }

            if (scene.Instantiate() is not BaseTile instance)
            {
                GD.PrintErr($"{LogPrefix} Failed to instantiate scene for: {tileType}");
                return false;
            }

            // Calculate position with proper offset based on building size
            var rotatedSize = building.GetRotatedSize(0f);
            var offset = GridUtils.GetCenterOffset(rotatedSize, 0f);
            
            instance.ZIndex = (int)building.Layer;
            instance.GlobalPosition = GridUtils.GridToWorld(cell) + offset;
            AddChild(instance);
            
            // Occupy the appropriate area based on building size
            Grid.OccupyArea(cell, instance, building.Size, 0f, building.Layer);
            
            return true;
        }
        catch (Exception e)
        {
            GD.PrintErr($"{LogPrefix} Error placing {tileType} at {cell}: {e.Message}");
            GD.Print($"{LogPrefix} Stack trace: {e.StackTrace}");
            return false;
        }
    }
}

public class ChunkData
{
    public List<Vector2I> StoneTiles { get; } = [];
    public List<Vector2I> IronTiles { get; } = [];
}