using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Firebase.Database;
using Firebase.Extensions;
using Newtonsoft.Json;
using TMPro;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// This is the game state manager on the phone side
/// </summary>
public class GameManager : MonoBehaviour
{
    public static GameManager Instance;

    private List<Player> players = new();
    public Player currentPlayer = null;

    [Header("Other component")]
    public float explanationTime = 4f;

    [Header("Home Connection Component")]
    public TMP_InputField roomCodeField;
    public TextMeshProUGUI roomError;
    public TMP_InputField playerNameField;
    public TextMeshProUGUI nameError;
    public Button submitNewPlayer;

    [Header("WaitingRoom Component")]
    public TextMeshProUGUI listPlayersUI;
    public GameObject submitStartGame;

    [Header("Explanation Component")]
    public TextMeshProUGUI counterExplanation;

    [Header("MakeProposition Component")]
    public TextMeshProUGUI counterMakeProposition;

    [Header("Vote Component")]
    public TextMeshProUGUI counterVote;

    [Header("EndGame Components")]
    [SerializeField] private GameObject endGameFirstPlayer;
    [SerializeField] private GameObject endGameOtherPlayers;

    [Header("Pages")]
    public GameObject HomeConnection;
    public GameObject WaitingRoom;
    public GameObject BeforeStart;
    public GameObject TakePicture;
    public GameObject VotePicture;
    public GameObject WaitingOtherPlayers;
    public GameObject EndGame;

    internal Room myRoom;

    private DatabaseReference realtimeDB;
    private DatabaseReference myOnlineRoom;
    private DateTime endOfViewDate = DateTime.MinValue;

    private void Awake()
    {
        submitNewPlayer.interactable = false;
        Instance = this;
        FirebaseInitializer.Instance.onFirebaseReady += Initialize;
    }

    private void Start()
    {
        InitializeHomePage();
    }

    private void InitializeHomePage()
    {
        HomeConnection.SetActive(true);
        WaitingRoom.SetActive(false);
        BeforeStart.SetActive(false);
        TakePicture.SetActive(false);
        VotePicture.SetActive(false);
        WaitingOtherPlayers.SetActive(false);
        EndGame.SetActive(false);
        submitStartGame.SetActive(false);
        submitNewPlayer.interactable = true;

        if (PlayerPrefs.HasKey("lastplayername"))
            playerNameField.text = PlayerPrefs.GetString("lastplayername");
    }

    private void OnApplicationQuit()
    {
        if (myOnlineRoom == null)
            return;

        myOnlineRoom.Child("players").Child(currentPlayer.id).RemoveValueAsync().ContinueWithOnMainThread(task =>
        {
            Debug.Log($"delete player {currentPlayer.name}");
            if (myOnlineRoom != null)
            {
                myOnlineRoom.ValueChanged -= OnRoomUpdate;
            }
        });
    }

    private void Update()
    {
        if (myRoom == null)
            return;

        if (myRoom.currentState == (int)GameState.Explanation && endOfViewDate != DateTime.MinValue)
        {
            TimeSpan duration = endOfViewDate - DateTime.Now;
            counterExplanation.text = ((int)duration.TotalSeconds).ToString("D1");

            if (duration.TotalMilliseconds <= 0)
            {
                Debug.Log("It's time to make proposition !");
                endOfViewDate = DateTime.MinValue;
            }
        }

        if (myRoom.currentState == (int)GameState.MakeProposition && endOfViewDate != DateTime.MinValue)
        {
            TimeSpan duration = endOfViewDate - DateTime.Now;
            counterMakeProposition.text = ((int)duration.TotalSeconds).ToString("D1");

            if (duration.TotalMilliseconds <= 0)
            {
                Debug.Log("It's time to finish proposition !");
                endOfViewDate = DateTime.MinValue;
            }
        }


        if (myRoom.currentState == (int)GameState.MakeVote && endOfViewDate != DateTime.MinValue)
        {
            TimeSpan duration = endOfViewDate - DateTime.Now;
            counterVote.text = ((int)duration.TotalSeconds).ToString("D1");

            if (duration.TotalMilliseconds <= 0)
            {
                Debug.Log("It's time to finish proposition !");
                endOfViewDate = DateTime.MinValue;
            }
        }

    }

    private void Initialize()
    {
        FirebaseInitializer.Instance.onFirebaseReady -= Initialize;
        realtimeDB = FirebaseDatabase.DefaultInstance.RootReference;
        submitNewPlayer.interactable = true;
        Debug.Log("Realtime DB initialized");
    }

    /// <summary>
    /// Send your name and game room to the server
    /// </summary>
    public void PlayerValidateNameAndServerRoom(string _name, string _code)
    {
        nameError.gameObject.SetActive(false);
        roomError.gameObject.SetActive(false);

        if (string.IsNullOrEmpty(_name))
        {
            Debug.LogError("Player name is empty", this);

            string errorText = LanguageManager.Instance.currentStringTable.GetTable().GetEntry("PhoneView/Canvas/Background/HomeState/NameField/ErrorNameLabel").LocalizedValue;
            nameError.text = errorText;
            nameError.gameObject.SetActive(true);
            submitNewPlayer.interactable = true;

            return;
        }

        if (string.IsNullOrEmpty(_code))
        {
            Debug.LogError("Room code is empty", this);

            string errorText = LanguageManager.Instance.currentStringTable.GetTable().GetEntry("PhoneView/Canvas/Background/HomeState/NameField/EmptyRoom").LocalizedValue;
            roomError.text = errorText;
            roomError.gameObject.SetActive(true);
            submitNewPlayer.interactable = true;

            return;
        }

        currentPlayer = new Player(_name);
        PlayerPrefs.SetString("lastplayername", _name);
        PlayerPrefs.Save();

        //check if the room exists, if not display an error message
        CheckIfRoomExists(_code, room =>
        {
            if (room == null)
            {
                Debug.LogError("The room doesn't exists");
                string errorText = LanguageManager.Instance.currentStringTable.GetTable().GetEntry("PhoneView/Canvas/Background/HomeState/NameField/ErrorRoom").LocalizedValue;
                roomError.text = errorText;
                roomError.gameObject.SetActive(true);
                submitNewPlayer.interactable = true;
            }
            else
            {
                myOnlineRoom = realtimeDB.Child("rooms").Child(_code);
                    //subscribe to it
                    myOnlineRoom.ValueChanged += OnRoomUpdate;
                //if room exists, join it
                JoinRoom(() =>
                {

                    myRoom.currentState = (int)GameState.WaitingForOtherPlayersToJoin;
                    players.Add(currentPlayer);

                    WaitingRoom.SetActive(true);
                    HomeConnection.SetActive(false);

                });
            }
        });
    }

    private void CheckIfRoomExists(string _roomCode, Action<Room> callback_Room)
    {
        realtimeDB.Child("rooms").Child(_roomCode).GetValueAsync().ContinueWithOnMainThread(task =>
        {
            if (task.IsFaulted)
            {
                Debug.LogException(task.Exception);
            }
            else if (task.IsCompleted)
            {
                DataSnapshot snapshot = task.Result;
                if (snapshot == null)
                {
                    callback_Room?.Invoke(null);
                }
                else
                {
                    callback_Room?.Invoke(JsonUtility.FromJson<Room>(snapshot.GetRawJsonValue()));
                }
            }
        });

    }

    /// <summary>
    /// Add this player to the room
    /// </summary>
    private void JoinRoom(Action callback_OnRoomJoined)
    {
        string JSON = JsonUtility.ToJson(currentPlayer);
        Debug.Log(JSON);
        try
        {
            myOnlineRoom.Child("players").Child(currentPlayer.id).SetRawJsonValueAsync(JSON).ContinueWithOnMainThread(task =>
            {
                if (task.IsFaulted)
                {
                    Debug.LogException(task.Exception);
                }
                else
                {
                    Debug.Log($"{currentPlayer.name} has been added to the room", this);
                    callback_OnRoomJoined?.Invoke();
                }
            });
        }
        catch (Exception ex)
        {
            Debug.LogException(ex);
        }
    }

    /// <summary>
    /// Call this only by the first player
    /// </summary>
    public void StartGame()
    {
        // send Start Game
        try
        {
            SendCurrentState(GameState.Explanation);
        }
        catch (Exception ex)
        {
            Debug.LogException(ex);
        }
    }

    /// <summary>
    /// Automatically called when something change in your room
    /// </summary>
    private void OnRoomUpdate(object sender, ValueChangedEventArgs e)
    {
        GameState lastState = myRoom != null ? (GameState)myRoom.currentState : GameState.EnteringName;

        try
        {
            if (e?.Snapshot?.GetRawJsonValue() != null)
            {
                string JSON = e.Snapshot.GetRawJsonValue();
                Debug.Log($"Room has been updated : {JSON}", this);
                myRoom = JsonConvert.DeserializeObject<Room>(e.Snapshot.GetRawJsonValue());
            }
        }
        catch (Exception ex)
        {
            Debug.LogException(ex);
        }
        if (myRoom == null)
        {
            Debug.LogError("Got an update for an empty room.", this);
            return;
        }


        Debug.Log($"lasState = {lastState}. Currentstep = {(GameState)myRoom.currentState}", this);
        if (myRoom.currentState != (int)lastState)
        {
            OnNewGameState();
        }

        //call this every time we are on this state
        switch (myRoom.currentState)
        {
            case (int)GameState.WaitingForOtherPlayersToJoin:
                {
                    List<Player> players = myRoom.GetOrderedPlayerList();
                    UpdateDisplayedListUser(players);

                    if (players.Count >= 3 && CheckIfIAmTheFirst(players))
                    {
                        submitStartGame.SetActive(true);
                    }
                    break;
                }
            default:
                break;
        }
    }

    /// <summary>
    /// Call this when Game state change
    /// </summary>
    private void OnNewGameState()
    {
        switch (myRoom.currentState)
        {
            case (int)GameState.EnteringName:
                {
                    InitializeHomePage();
                    break;
                }
            case (int)GameState.Explanation:
                {
                    Debug.Log("Explanation time.", this);

                    WaitingOtherPlayers.SetActive(false);
                    TakePicture.SetActive(false);
                    WaitingRoom.SetActive(false);
                    VotePicture.SetActive(false);
                    EndGame.SetActive(false);

                    BeforeStart.SetActive(true);
                    endOfViewDate = DateTime.Now.AddSeconds(4);
                    break;
                }
            case (int)GameState.MakeProposition:
                {
                    Debug.Log("It's photo time !", this);
                    WaitingOtherPlayers.SetActive(false);
                    BeforeStart.SetActive(false);
                    WaitingRoom.SetActive(false);
                    VotePicture.SetActive(false);
                    EndGame.SetActive(false);

                    TakePicture.SetActive(true);
                    endOfViewDate = DateTime.Now.AddSeconds(60);
                    break;
                }
            case (int)GameState.MakeVote:
                {
                    Debug.Log("It's voting time !", this);
                    WaitingOtherPlayers.SetActive(false);
                    BeforeStart.SetActive(false);
                    WaitingRoom.SetActive(false);
                    TakePicture.SetActive(false);
                    EndGame.SetActive(false);

                    VotePicture.SetActive(true);
                    endOfViewDate = DateTime.Now.AddSeconds(20);
                    Debug.Log("subscribe to question ID");
                    myOnlineRoom.Child("currentQuestionId").ValueChanged += OnCurrentQuestionChanged;
                    break;
                }
            case (int)GameState.Score:
                {
                    Debug.Log("it's scoring time !", this);
                    Debug.Log("unsubscribe to question ID");
                    myOnlineRoom.Child("currentQuestionId").ValueChanged -= OnCurrentQuestionChanged;

                    WaitingOtherPlayers.SetActive(false);
                    BeforeStart.SetActive(false);
                    WaitingRoom.SetActive(false);
                    TakePicture.SetActive(false);
                    VotePicture.SetActive(false);

                    EndGame.SetActive(true);

                    endGameFirstPlayer.SetActive(CheckIfIAmTheFirst(myRoom.GetPlayerList()));
                    endGameOtherPlayers.SetActive(!CheckIfIAmTheFirst(myRoom.GetPlayerList()));
                    break;
                }

        }
    }

    /// <summary>
    /// Call this to wait for other players
    /// </summary>
    public void WaitForPlayers()
    {
        if (TakePicture.activeInHierarchy)
        {
            TakePicture.SetActive(false);
        }
        if (VotePicture.activeInHierarchy)
        {
            VotePicture.SetActive(false);
        }
        WaitingOtherPlayers.SetActive(true);
    }


    private void UpdateDisplayedListUser(List<Player> players)
    {
        listPlayersUI.text = string.Empty;
        for (int i = 0; i < players.Count; i++)
        {
            listPlayersUI.text += "\n" + players[i].name;
        }
    }

    /// <summary>
    /// return true if you are the fist player
    /// </summary>
    /// <param name="players"></param>
    /// <returns></returns>
    private bool CheckIfIAmTheFirst(List<Player> players)
    {
        bool isFirst = false;
        if (players.Count > 1)
        {

            List<Player> sortedList = players.OrderBy(x => x.creationDate).ToList();

            if (sortedList[0].id == currentPlayer.id)
            {
                isFirst = true;
            }
        }
        return isFirst;
    }

    public void SendCurrentState(GameState state, Action callback_onCurrentStateSent = null)
    {
        myOnlineRoom.Child("currentState").SetValueAsync((int)state).ContinueWithOnMainThread(task =>
        {
            if (task.IsFaulted)
            {
                Debug.LogException(task.Exception);
            }
            else
            {
                callback_onCurrentStateSent?.Invoke();
            }
        });
    }

    public void OnClickSubmitSignIn()
    {
        string playerName = playerNameField.text;
        string roomCode = roomCodeField.text;
        submitNewPlayer.interactable = false;
        PlayerValidateNameAndServerRoom(playerName, roomCode);
    }

    public void onClickSamePlayers()
    {
        Debug.Log("Play with same players !", this);
        Room newRoom = myRoom.Copy();
        newRoom.currentState = (int)GameState.Explanation;
        newRoom.questions.Clear();
        newRoom.currentQuestionId = 0;
        string json = JsonConvert.SerializeObject(newRoom);

        myOnlineRoom.SetRawJsonValueAsync(json).ContinueWithOnMainThread(task =>
        {
            if (task.IsFaulted)
            {
                Debug.LogException(task.Exception);
            }
        });

    }
    public void onClickNewPlayers()
    {
        Debug.Log("Play with new players !", this);
        Room newRoom = myRoom.Copy();
        newRoom.currentState = (int)GameState.EnteringName;
        newRoom.currentQuestionId = 0;
        newRoom.questions = null;
        newRoom.players = null;
        string json = JsonConvert.SerializeObject(newRoom);

        myOnlineRoom.SetRawJsonValueAsync(json).ContinueWithOnMainThread(task =>
        {
            if (task.IsFaulted)
            {
                Debug.LogException(task.Exception);
            }
        });
    }


    private void OnCurrentQuestionChanged(object sender, ValueChangedEventArgs onlineValue)
    {
        if (onlineValue == null || onlineValue.Snapshot == null || onlineValue.Snapshot.Value == null || myRoom.questions == null || myRoom.questions.Count == 0)
            return;

        int questionId = onlineValue.Snapshot.Value.ConvertTo<int>();
        Debug.Log($"new question ! Q{(onlineValue.Snapshot.Value.ConvertTo<int>())}");

        Question q = myRoom.questions[questionId];

        //do not vote for your question
        if (myRoom.GetQuestionsByPlayer(currentPlayer).Contains(q))
        {
            WaitingOtherPlayers.SetActive(true);
            VotePicture.SetActive(false);
        }
        else
        {
            VotePicture.SetActive(true);
            WaitingOtherPlayers.SetActive(false);
            endOfViewDate = DateTime.Now.AddSeconds(20);
            VotePicture.GetComponent<PropositionHandler>().ShowPlayersProposition(q);
        }
    }

    /// <summary>
    /// Call this function from the vote button in the scene
    /// </summary>
    /// <param name="propositionNumber"></param>
    public void OnClickProposition(int propositionNumber)
    {
        /*
        Debug.Log($"Room has {myRoom.questions.Count} questions. the current Question is Q({myRoom.currentQuestionId}).");
        Debug.Log($"Q({myRoom.currentQuestionId}) has {myRoom.questions[myRoom.currentQuestionId].propositions.Count} propositions.");
        Debug.Log($"Player click on proposition {propositionNumber}.");
        Debug.Log($"it has {myRoom.questions[myRoom.currentQuestionId].propositions[propositionNumber].voters.Count} voters.");
        */

        List<string> voters = myRoom.questions[myRoom.currentQuestionId].propositions[propositionNumber].voters;
        if (voters == null)
            voters = new List<string>();

        voters.Add(currentPlayer.id);
        myOnlineRoom.Child("questions").Child(myRoom.currentQuestionId.ToString()).Child("propositions").Child(propositionNumber.ToString()).Child("voters").SetValueAsync(voters);

        WaitingOtherPlayers.SetActive(true);
        VotePicture.SetActive(false);
    }


}

public enum GameState
{
    EnteringName = 0,
    WaitingForOtherPlayersToJoin = 1,
    Explanation = 2,
    MakeProposition = 3,
    MakeVote = 4,
    Score = 5
}
