using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.SceneManagement;

[Serializable]
public class ReplayRecord
{
    public string gameDate;
    public string playerA;
    public string playerB;
    public Enums.GameResult gameResult;  
    public int playerAPofileImageIndex;
    public int playerBPofileImageIndex;
    public List<Move> moves = new List<Move>();
}
[Serializable]
public class Move
{
    public string stoneType;
    public int columnIndex;
    public int rowIndex;

    public Move(string stoneType, int columnIndex,int rowIndex)
    {
        this.stoneType = stoneType;
        this.columnIndex = columnIndex;
        this.rowIndex = rowIndex;
    }
}

public class ReplayManager : Singleton<ReplayManager>
{
    private ReplayRecord _recordingReplayData;
    private Coroutine finishCoroutine;

    #region 기보 시작 후 데이터를 컨트롤하기
    
    private ReplayRecord _selectedReplayRecord;
    
    //DO, Undo를 위한 스택
    private Stack<Move> _placedStoneStack;
    private Stack<Move> _undoStack;
    private int _moveIndex;
    
    private GameLogic _gameLogic;
    private StoneController _stoneController;
    
    public void InitReplayBoard(ReplayRecord replayRecord)
    {        
        _selectedReplayRecord = replayRecord;
        _moveIndex = 0;
        
        _placedStoneStack = new Stack<Move>();
        _undoStack = new Stack<Move>();
    }

    public Move GetNextMove()
    {
        if (_undoStack.Count > 0)
            return _undoStack.Pop();
        
        if(_moveIndex >= _selectedReplayRecord.moves.Count)
            return null;
        
        Move move = _selectedReplayRecord.moves[_moveIndex];
        _moveIndex++;
        return move;
    }

    public void PushMove(Move storedMove)
    {
        _placedStoneStack.Push(storedMove);
    }

    public Move PopPlacedMove()
    {
        if (_placedStoneStack.Count == 0)
            return null;
        Move move = _placedStoneStack.Pop();
        return move;
    }
    
    private void PushUndoMove(Move storedMove)
    {
        _undoStack.Push(storedMove);
    }
    
    #endregion
    
    #region 게임 플레이중 기보 데이터 저장
    ///<summary>
    /// 게임 시작에 호출해서 기보 데이터 초기화
    /// </summary>
    public void InitReplayData(string playerANickname="", string playerBNickname="", int playerAProfileIndex=0, int playerBProfileIndex=0)
    {
        _recordingReplayData = new ReplayRecord();
        _recordingReplayData.playerA = playerANickname;
        _recordingReplayData.playerB = playerBNickname;
        _recordingReplayData.playerAPofileImageIndex = playerAProfileIndex;
        _recordingReplayData.playerBPofileImageIndex = playerBProfileIndex;
    }
    
    ///<summary>
    /// 게임 씬에서 착수를 할 때마다 호출해서 기록
    /// </summary>
    public void RecordStonePlaced(Enums.StoneType stoneType,int row, int col)
    {
        string stoneColor = stoneType.ToString();
        if (_recordingReplayData == null) return;
        _recordingReplayData.moves.Add(new Move(stoneColor, row, col));
    }
    
    /// <summary>
    /// 게임 종료 후 호출하여 리플레이 데이터를 저장합니다.
    /// </summary>
    public void SaveReplayDataResult(Enums.GameResult gameResultType)
    {
        try
        {
            string time = DateTime.Now.ToString(("yyyy-MM-dd HH_mm_ss"));
            _recordingReplayData.gameDate = time;
            _recordingReplayData.gameResult = gameResultType;
            
            // Json데이터로 변환해서 저장
            string json = JsonUtility.ToJson(_recordingReplayData, true);
            string path = Path.Combine(Application.persistentDataPath, $"{time}.json");
            File.WriteAllText(path, json);

            //최신 데이터 10개만 유지되도록 저장
            RecordCountChecker();
        }
        catch(Exception e)
        {
            Debug.LogError($"An error occurred while saving replay data:{e.Message}");
        }
    }

    #endregion
    
    //폴더내 기보 파일을 전부 읽어옵니다.
    public List<ReplayRecord> LoadReplayDatas()
    {
        List<ReplayRecord> records = new List<ReplayRecord>();
        string path = Application.persistentDataPath;

        try
        {
            var files = Directory.GetFiles(path, "*.json");
            foreach (var file in files)
            {
                try
                {
                    ReplayRecord record = JsonUtility.FromJson<ReplayRecord>(File.ReadAllText(file));
                    records.Add(record);
                }
                catch (Exception e)
                {
                    Debug.LogError($"Replaydata cannot be converted to JSON: {e.Message}");
                }
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"Replay Directory Error: {e.Message}");
        }

        return records;
    }
    
    // 최대 저장 개수만큼 기보데이터가 저장, 유지되도록 하는 함수
    private void RecordCountChecker()
    {
        try
        {
            string path = Application.persistentDataPath;
            var files = Directory.GetFiles(path, "*.json");
            if (files.Length <= Constants.ReplayMaxRecordSize)
                return;
            File.Delete(files[0]);
            RecordCountChecker();
        }
        catch (Exception e)
        {
            Debug.LogError($"Replay Directory Error: {e.Message}");
        }
    }

    // 기보 데이터 하나를 선택해서 매니저에 저장(씬 이동 후 데이터 활용을 위해)
    public void SetReplayData(ReplayRecord replayRecord)
    {
        _selectedReplayRecord = replayRecord;
    }

    #region ReplayController에서 호출할 함수들
    public void ReplayNext(Move nextMove)
    {
        // 보드에 돌을 설정하기 위해 gameLogic의 SetNewBoardValue호출
        if (nextMove.stoneType.Equals(Enums.StoneType.Black.ToString()))
        {
            _gameLogic.SetNewBoardValue(Enums.PlayerType.PlayerA, nextMove.columnIndex, nextMove.rowIndex);

        }
        else if (nextMove.stoneType.Equals(Enums.StoneType.White.ToString()))
        {
            _gameLogic.SetNewBoardValue(Enums.PlayerType.PlayerB, nextMove.columnIndex, nextMove.rowIndex);
        }
        // 돌이 놓인 내역을 ReplayManager에도 반영
        PushMove(nextMove);
    }

    public void ReplayUndo(Move targetMove)
    {
        PushUndoMove(targetMove);
        _gameLogic.RemoveStone(targetMove.columnIndex, targetMove.rowIndex);
        if (_placedStoneStack.Count > 0)
        {
            var undoLastMove = _placedStoneStack.Peek();
            _gameLogic.StoneController.SetStoneState(Enums.StoneState.LastPositioned, undoLastMove.columnIndex, undoLastMove.rowIndex);
            _gameLogic.SetLastPositioned(undoLastMove.columnIndex, undoLastMove.rowIndex);
        }
        else
        {
            _gameLogic.SetLastPositioned(-1, -1);
        }
    }

    public void ReplayFirst()
    {
        while (_placedStoneStack.Count > 0)
        {
            ReplayUndo(_placedStoneStack.Pop());
        }
    }
    
    public void ReplayFinish(Action onFinishEnd)
    {
        finishCoroutine = StartCoroutine(IReplayFinish(onFinishEnd));
    }

    /// <summary>
    /// 끝 버튼을 중간에 멈춰야 할 때 사용
    /// </summary>
    /// <param name="onFinishStop">마지막 장면까지 가는 도중에 멈출경우 해야할 일 전달</param>
    public void StopReplayFinish(Action onFinishStop)
    {
        if (finishCoroutine != null)
        {
            StopCoroutine(finishCoroutine);
            finishCoroutine = null;
        }
        onFinishStop?.Invoke();
    }

    private IEnumerator IReplayFinish(Action onFinishEnd)
    {
        while(_placedStoneStack.Count < _selectedReplayRecord.moves.Count)
        {
            ReplayNext(GetNextMove());
            yield return new WaitForSeconds(0.1f);
        }
        onFinishEnd?.Invoke();
    }
    public string GetPlayerANickname()
    {
        return _selectedReplayRecord.playerA;
    }

    public string GetPlayerBNickname()
    {
        return _selectedReplayRecord.playerB;
    }

    public int GetPlayerAProfileIndex()
    {
        return _selectedReplayRecord.playerAPofileImageIndex;
    }

    public int GetPlayerBProfileIndex()
    {
        return _selectedReplayRecord.playerBPofileImageIndex;
    }
    #endregion

    protected override void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    {
        if (scene.name == "Replay")
        {
            InitReplayBoard(_selectedReplayRecord);

            _stoneController = GameObject.FindObjectOfType<StoneController>();
            _stoneController.InitStones();
            _gameLogic = new GameLogic(_stoneController, Enums.GameType.Replay);
        }
    }
}
