using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using Core.Domain;
using UnityEngine;
using Random = UnityEngine.Random;

namespace Infrastructure.Unity
{
    public class LevelGenerator : MonoBehaviour
    {
        [Header("Prefabs")]
        [SerializeField] private TileViewAdapter tilePrefab;
        [SerializeField] private ParticleSystem tileBreakVfxPrefab;
        [SerializeField] private JumpPadAdapter jumpPadPrefab;
        [SerializeField] private TeleporterAdapter teleporterPrefab;

        [Header("Settings")]
        [SerializeField] private int gridSizeX = 10;
        [SerializeField] private int gridSizeY = 10;
        [SerializeField] private int floorsCount = 3;
        [SerializeField] private float floorHeightDistance = 15f;
        [SerializeField] private float decayTime = 0.5f;
        [SerializeField] private float fallingTime = 2.0f;
        [SerializeField] private float minimumDistanceBetweenTeleporters = 3f;

        public float FloorHeightDistance => floorHeightDistance;
        public int FloorsCount => floorsCount;
        public int GridSizeX => gridSizeX;
        public int GridSizeY => gridSizeY;

        private TilePool _tilePool;
        
        public IEnumerator GenerateAsync(SoundManager soundManager, List<Tile> allTiles, Dictionary<string, TileViewAdapter> tileViews, CameraController camera, RumbleManager rumble, Action onComplete)
        {
            _tilePool = new TilePool(tilePrefab, transform);
            
            var stopwatch = new Stopwatch();
            stopwatch.Start();

            yield return GenerateFloorAsync(0, MapPatterns.GenerateSquare(gridSizeX, gridSizeY), soundManager, allTiles, tileViews, camera, rumble, stopwatch);
            yield return GenerateFloorAsync(1, MapPatterns.GenerateDonut(gridSizeX, Mathf.FloorToInt(gridSizeX / 3f)), soundManager, allTiles, tileViews, camera, rumble, stopwatch);
            yield return GenerateFloorAsync(2, MapPatterns.GenerateCircle(gridSizeX), soundManager, allTiles, tileViews, camera, rumble, stopwatch);
            
            stopwatch?.Stop();
            onComplete?.Invoke();
        }

        private IEnumerator GenerateFloorAsync(int floorIndex, List<Vector2Int> coordinates, SoundManager soundManager,
            List<Tile> allTiles, Dictionary<string, TileViewAdapter> tileViews, CameraController camera, RumbleManager rumble, Stopwatch stopwatch)
        {
            var yOffset = -(floorIndex * floorHeightDistance);
            var xOffset = gridSizeX / 2f;
            var zOffset = gridSizeY / 2f;
            
            const long frameBudgetMs = 5;

            foreach (var coord in coordinates)
            {
                var pos = new Vector3(coord.x - xOffset, yOffset, coord.y - zOffset);
                CreateTile(pos, $"{floorIndex}_{coord.x}_{coord.y}", floorIndex, soundManager, allTiles, tileViews, camera, rumble);
                
                if (stopwatch.ElapsedMilliseconds > frameBudgetMs)
                {
                    stopwatch.Reset();
                    stopwatch.Start();
                    yield return null; // Wait for next frame
                }
            }

            if (floorIndex > 0 && jumpPadPrefab)
            {
                var validSpot = coordinates[Random.Range(0, coordinates.Count)];
                var padPos = new Vector3(validSpot.x - xOffset, yOffset + 0.6f, validSpot.y - zOffset);

                Instantiate(jumpPadPrefab, padPos, Quaternion.identity, transform);
            }

            if (floorIndex > 0 && teleporterPrefab && coordinates.Count > 5)
            {
                // Spawn a pair of teleporters
                var indexA = Random.Range(0, coordinates.Count);
                int indexB;
                do
                {
                    indexB = Random.Range(0, coordinates.Count);
                } while (indexB == indexA);

                var spotA = coordinates[indexA];
                var spotB = coordinates[indexB];

                var posA = new Vector3(spotA.x - xOffset, yOffset + 0.6f, spotA.y - zOffset);
                var posB = new Vector3(spotB.x - xOffset, yOffset + 0.6f, spotB.y - zOffset);
                
                // check if positions are not too close
                if (Vector3.Distance(posA, posB) < minimumDistanceBetweenTeleporters)
                {
                    // find a new position for teleporter B
                    for (var i = 0; i < coordinates.Count; i++)
                    {
                        if (i == indexA) continue;
                        
                        var newSpotB = coordinates[i];
                        var newPosB = new Vector3(newSpotB.x - xOffset, yOffset + 0.6f, newSpotB.y - zOffset);
                        if (Vector3.Distance(posA, newPosB) >= minimumDistanceBetweenTeleporters)
                        {
                            posB = newPosB;
                            break;
                        }
                    }
                }

                var telA = Instantiate(teleporterPrefab, posA, Quaternion.identity, transform);
                var telB = Instantiate(teleporterPrefab, posB, Quaternion.identity, transform);

                telA.Initialize(telB.transform);
                telB.Initialize(telA.transform);
                
                System.Action onTeleport = () => 
                {
                    camera?.Shake(0.2f, 0.2f);
                    rumble?.PulseMedium();
                };

                telA.OnTeleport += onTeleport;
                telB.OnTeleport += onTeleport;
            }
        }

        private void CreateTile(Vector3 position, string id, int floorIndex, SoundManager soundManager,
            List<Tile> allTiles, Dictionary<string, TileViewAdapter> tileViews, CameraController camera, RumbleManager rumble)
        {
            var go = _tilePool.Get();
            go.transform.position = position;
            go.transform.rotation = Quaternion.identity;
            go.transform.localScale = Vector3.one * 0.95f;

            // 15% chance to be Fragile
            var isFragile = Random.value < 0.15f;
            var type = isFragile ? TileType.Fragile : TileType.Normal;

            var tileLogic = new Tile(id, floorIndex, decayTime, fallingTime, type);

            go.Initialize(tileLogic, (t) => _tilePool.Return(t));

            tileLogic.OnStateChanged += (t) =>
            {
                if (t.CurrentState == TileState.Warning)
                {
                    soundManager?.PlayTileWarning(position);
                }
                else if (t.CurrentState == TileState.Falling)
                {
                    soundManager?.PlayTileBreak(position);
                    if (t.Type == TileType.Fragile)
                    {
                        camera?.Shake(0.1f, 0.05f);
                        rumble?.PulseLight();
                    }

                    if (tileBreakVfxPrefab)
                    {
                        var vfx = Instantiate(tileBreakVfxPrefab, position, Quaternion.identity);
                        var main = vfx.main;
                    
                        var currentColor = go.MeshRenderer.material.color;
                        main.startColor = currentColor;
                    
                        Destroy(vfx.gameObject, 2f);
                    }
                }
            };

            allTiles.Add(tileLogic);
            tileViews.Add(id, go);
        }
    }
}