using System.Collections.Generic;
using System.Linq;
using Enums;
using Models.Interfaces;
using Services.Interfaces;
using Structs;
using UnityEngine;

namespace Services {
    public class MatchService : IMatchService {
        private List<Gem> currentMatches = new List<Gem>();
        public List<Gem> CurrentMatches => this.currentMatches;

        public List<BombSpawnRequest> pendingBombSpawns = new List<BombSpawnRequest>();
        public IReadOnlyList<BombSpawnRequest> PendingBombSpawns => this.pendingBombSpawns;

        private Vector2Int lastSwapFrom;
        private Vector2Int lastSwapTo;
        
        private IGameBoard gameBoard;

        public MatchService(IGameBoard gameBoard) {
            this.gameBoard = gameBoard;
        }

        public void SetLastSwap(Vector2Int from, Vector2Int to) {
            this.lastSwapFrom = from;
            this.lastSwapTo = to;
        }

        public void ClearPendingBombs() {
            this.pendingBombSpawns.Clear();
        }
        
        public bool MatchesAt(Vector2Int positionToCheck, GemType gemTypeToCheck) {
            Gem[,] gems = this.gameBoard.GemsGrid;
            
            if (gemTypeToCheck == GemType.Bomb)
                return false;
            
            if (positionToCheck.x > 1)
            {
                if (gems[positionToCheck.x - 1, positionToCheck.y].Type == gemTypeToCheck && gems[positionToCheck.x - 2, positionToCheck.y].Type == gemTypeToCheck)
                    return true;
            }

            if (positionToCheck.y > 1)
            {
                if (gems[positionToCheck.x, positionToCheck.y - 1].Type == gemTypeToCheck && gems[positionToCheck.x, positionToCheck.y - 2].Type == gemTypeToCheck)
                    return true;
            }

            return false;
        }

        public void FindAllMatches() {
            this.currentMatches.Clear();
            this.pendingBombSpawns.Clear();

            for (int x = 0; x < this.gameBoard.Width; x++)
                for (int y = 0; y < this.gameBoard.Height; y++) {
                    Gem currentGem = this.gameBoard.GemsGrid[x, y];
                    if (currentGem == null)
                        continue;

                    if (x > 0 && x < this.gameBoard.Width - 1) {
                        Gem leftGem = this.gameBoard.GemsGrid[x - 1, y];
                        Gem rightGem = this.gameBoard.GemsGrid[x + 1, y];
                        if (leftGem != null && rightGem != null) {
                            if (leftGem.MatchColor == currentGem.MatchColor && rightGem.MatchColor == currentGem.MatchColor) {
                                this.currentMatches.Add(currentGem);
                                this.currentMatches.Add(leftGem);
                                this.currentMatches.Add(rightGem);
                            }
                        }
                    }

                    if (y > 0 && y < this.gameBoard.Height - 1) {
                        Gem aboveGem = this.gameBoard.GemsGrid[x, y - 1];
                        Gem bellowGem = this.gameBoard.GemsGrid[x, y + 1];
                        if (aboveGem != null && bellowGem != null) {
                            if (aboveGem.MatchColor == currentGem.MatchColor && bellowGem.MatchColor == currentGem.MatchColor) {
                                this.currentMatches.Add(currentGem);
                                this.currentMatches.Add(aboveGem);
                                this.currentMatches.Add(bellowGem);
                            }
                        }
                    }
                }

            if (this.currentMatches.Count > 0)
                this.currentMatches = this.currentMatches.Distinct().ToList();

            DetectBombSpawnFromLastSwap();
        }
        
        private void DetectBombSpawnFromLastSwap() {
            Vector2Int from = this.lastSwapFrom;
            Vector2Int to = this.lastSwapTo;

            TryCreateBombSpawnAt(from);
            TryCreateBombSpawnAt(to);
        }
        
        private void TryCreateBombSpawnAt(Vector2Int pivot) {
            Gem pivotGem = this.gameBoard.GetGemAt(pivot);
            if (pivotGem == null)
                return;

            // If it's already a bomb, don't create another.
            if (pivotGem.Type == GemType.Bomb)
                return;

            if (this.currentMatches.All(g => g.Position != pivot))
                return;

            int horizontal = CountLine(pivot, Vector2Int.left) + 1 + CountLine(pivot, Vector2Int.right);
            int vertical = CountLine(pivot, Vector2Int.up) + 1 + CountLine(pivot, Vector2Int.down);

            int best = Mathf.Max(horizontal, vertical);
            if (best < 4)
                return;

            // Spawn a bomb on the creating slot with the same color group.
            // Prevent duplicates for the same cell.
            if (this.pendingBombSpawns.Any(b => b.Position == pivot))
                return;

            this.pendingBombSpawns.Add(new BombSpawnRequest(pivot, pivotGem.MatchColor));
        }
        
        private int CountLine(Vector2Int start, Vector2Int direction) {
            Gem startGem = this.gameBoard.GetGemAt(start);
            if (startGem == null)
                return 0;

            GemType color = startGem.MatchColor;

            int count = 0;
            Vector2Int position = start + direction;

            while (position.x >= 0 && position.x < this.gameBoard.Width && position.y >= 0 && position.y < this.gameBoard.Height) {
                Gem g = this.gameBoard.GetGemAt(position);
                if (g == null || g.MatchColor != color)
                    break;

                count++;
                position += direction;
            }

            return count;
        }

        private void CheckForBombs() {
            Gem[,] gems = this.gameBoard.GemsGrid;
            int width = this.gameBoard.Width;
            int height = this.gameBoard.Height;
            
            for (int i = 0; i < this.currentMatches.Count; i++)
            {
                Gem gem = this.currentMatches[i];
                int x = gem.Position.x;
                int y = gem.Position.y;

                Vector2Int[] directions = 
                {
                    Vector2Int.left,
                    Vector2Int.right,
                    Vector2Int.down,
                    Vector2Int.up
                };

                foreach (Vector2Int direction in directions)
                {
                    int newX = x + direction.x;
                    int newY = y + direction.y;

                    if (newX < 0 || newX >= width || newY < 0 || newY >= height)
                        continue;

                    Gem neighbor = gems[newX, newY];
                    if (neighbor?.Type == GemType.Bomb)
                        MarkBombCross(new Vector2Int(newX, newY), 1);
                }
            }
        }
        
        private void MarkBombCross(Vector2Int bombPosition, int radius) {
            Gem[,] gems = this.gameBoard.GemsGrid;
            int width = this.gameBoard.Width;
            int height = this.gameBoard.Height;

            void Mark(Vector2Int p) {
                if (p.x < 0 || p.x >= width || p.y < 0 || p.y >= height)
                    return;
                Gem g = gems[p.x, p.y];
                if (g == null)
                    return;

                this.currentMatches.Add(g);
            }

            Mark(bombPosition);

            for (int i = 1; i <= radius; i++) {
                Mark(bombPosition + Vector2Int.left * i);
                Mark(bombPosition + Vector2Int.right * i);
                Mark(bombPosition + Vector2Int.up * i);
                Mark(bombPosition + Vector2Int.down * i);
            }

            this.currentMatches = this.currentMatches.Distinct().ToList();
        }
    }
    
    
}