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

public class RoomManager : MonoBehaviour
{
    [Header("Waiting For Players Page")]
    public GameObject waitingForPlayersPage;
    /// <summary>
    /// TextMeshPro that show the value of the current rooom code
    /// </summary>
    public TextMeshProUGUI roomCodeLabel;
    public List<TextMeshProUGUI> waitingForPlayersLabels = new List<TextMeshProUGUI>();
    public PromptList promptList;

    [Header("Waiting For Proposition Page")]
    public GameObject waitingForPropositionsPage;
    public List<TextMeshProUGUI> waitingForPropositionsLabels = new List<TextMeshProUGUI>();
    public float propositionTime = 60;
    private float propositionCurrentTime = 0;
    public TextMeshProUGUI timerGUI;

    private GameState currentState;
    private Room myRoom = null;

    public float votingTime = 20;

    private float votingCurrentTime = 0;

    /// <summary>
    /// Contain the infos about the current displayed question (during votes)
    /// </summary>
    private Question currentQuestion;

    private List<Question> questions;

    /// <summary>
    /// When this is equal to questions.Count, go to score page
    /// </summary>
    private int numberOfQuestionVoted = 0;

    DatabaseReference realtimeDB;

    private void Awake()
    {
        FirebaseInitializer.Instance.onFirebaseReady += Initialize;
        currentState = GameState.EnteringName;
    }


    private void Start()
    {
        propositionCurrentTime = propositionTime;
        votingCurrentTime = votingTime;
        DisableAllPlayerLabels();
        currentState = GameState.WaitingForOtherPlayersToJoin;
    }

    private void DisableAllPlayerLabels()
    {
        for (int i = 0; i < waitingForPlayersLabels.Count; i++)
        {
            waitingForPlayersLabels[i].text = $"Waiting for P{i + 1}";
        }
    }

    private void OnApplicationQuit()
    {
        realtimeDB.Child("rooms").Child(myRoom.code).RemoveValueAsync();
        Debug.Log($"delete room {myRoom.code}");
        myRoom = null;
    }

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

    /// <summary>
    /// Check all the rooms in the server and give back the number already taken
    /// </summary>
    private void WhichCodesAreAlreadyUsed(Action<List<int>> callback_OnCodesChecked)
    {
        Debug.Log("Checking other rooms to get which codes are already used", this);
        List<int> alreadyUsedCodes = new List<int>();
        try
        {
            realtimeDB.Child("rooms").GetValueAsync().ContinueWithOnMainThread(task =>
            {
                Debug.Log("looking into the online rooms", this);
                if (task.IsFaulted)
                {
                    Debug.LogException(task.Exception);
                }
                else if (task.IsCompleted)
                {
                    DataSnapshot snapshot = task.Result;
                    if (snapshot.Value != null)
                    {
                        string JSON = snapshot.GetRawJsonValue();
                        Debug.Log($"found some rooms :\n{JSON}", this);
                        Dictionary<string, Room> onlineRooms = JsonConvert.DeserializeObject<Dictionary<string, Room>>(JSON);
                        foreach (Room r in onlineRooms.Values)
                        {
                            Debug.Log($"Code {r.code} is already used by another party", this);
                            alreadyUsedCodes.Add(int.Parse(r.code));
                        }
                    }
                    else
                    {
                        Debug.Log($"Your party is the first one!", this);
                    }
                }
                callback_OnCodesChecked?.Invoke(alreadyUsedCodes);
            });
        }
        catch (Exception ex)
        {
            Debug.LogException(ex);
        }

    }

    /// <summary>
    /// Automatically called at start of game
    /// </summary>
    [ContextMenu("Create New Room")]
    public void CreateNewRoom()
    {
        WhichCodesAreAlreadyUsed(codes =>
        {
            Debug.Log("coucou");
            Room newRoom = new Room(GenerateRandomAvailableCode(codes).ToString("D4"));
            myRoom = newRoom;
            try
            {
                string JSON = JsonConvert.SerializeObject(newRoom);

                realtimeDB.Child("rooms").Child(newRoom.code).SetRawJsonValueAsync(JSON).ContinueWithOnMainThread(task =>
                {
                    //then subscribe to it
                    realtimeDB.Child("rooms").Child(newRoom.code).ValueChanged += OnRoomUpdate;
                    roomCodeLabel.text = myRoom.code;
                    Debug.Log($"room {myRoom.code} has been created on the server");
                });
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }
        });
    }

    /// <summary>
    /// Generate a code between 0 and 1000 that is not in the list
    /// </summary>
    /// <param name="_impossibleCodes">the list of code you don"t want to get</param>
    /// <returns></returns>
    private int GenerateRandomAvailableCode(List<int> _impossibleCodes)
    {
        int random = UnityEngine.Random.Range(0, 1000);
        while (_impossibleCodes.Contains(random))
        {
            Debug.Log($"{random} is already taken, choosing another room code", this);
            random = UnityEngine.Random.Range(0, 1000);
        }
        return random;
    }

    public void PlayerSendProposition(Proposition _proposition)
    {

    }

    /// <summary>
    /// Called when the first player clicked "Start"
    /// </summary>
    public void HostStartGame()
    {
        currentState = GameState.MakeProposition;
        waitingForPropositionsPage.SetActive(true);
        waitingForPlayersPage.SetActive(false);
        propositionCurrentTime = propositionTime;
    }

    /// <summary>
    /// Start the proposition timer
    /// </summary>
    public void StartPropositionTimer()
    {

    }


    /// <summary>
    /// Automatically called when the proposition timer has finished
    /// </summary>
    public void PropositionTimerFinished()
    {

    }

    /// <summary>
    /// Start the voting timer
    /// </summary>
    public void StartVotingTimer()
    {

    }


    /// <summary>
    /// Automatically called when the voting timer has finished
    /// </summary>
    public void VotingTimerFinished()
    {

    }

    /// <summary>
    /// Automatically called when a proposition is updated (someone has voted or a picture has been proposed)
    /// </summary>
    public void OnPropositionUpdate()
    {

    }

    public void GeneratePrompts()
    {
        System.Random rnd = new();
        List<Prompt> prompts = promptList.prompts.OrderBy(x => rnd.Next()).Take(myRoom.players.Count() * 2).ToList();
        List<Player> fullPlayers = myRoom.players.Values.ToList().Concat(myRoom.players.Values.ToList()).OrderBy(x => rnd.Next()).ToList();
        Dictionary<string, Question> questions = new();

        foreach (Prompt prompt in prompts)
        {
            Dictionary<string, Proposition> propositions = new();
            Player basePlayer = fullPlayers[0];
            propositions.Add(Guid.NewGuid().ToString(), new Proposition() { owner = basePlayer });

            for (int i = 1; i < fullPlayers.Count(); i++)
            {
                Player secondPlayer = fullPlayers[i];

                if (basePlayer.id != secondPlayer.id)
                {
                    propositions.Add(Guid.NewGuid().ToString(), new Proposition() { owner = secondPlayer });
                    fullPlayers.RemoveAt(0);
                    break;
                }
            }

            questions.Add(Guid.NewGuid().ToString(), new Question()
            {
                promptId = prompt.id,
                propositions = propositions,
                creationDate = DateTime.Now.ToOADate(),
            });
        }

        string JSON = JsonConvert.SerializeObject(questions);
        realtimeDB.Child("rooms").Child(myRoom.code).Child("questions").SetRawJsonValueAsync(JSON);
    }

    /// <summary>
    /// Automatically called when something change in your room
    /// </summary>
    private void OnRoomUpdate(object sender, ValueChangedEventArgs value)
    {
        if (value.DatabaseError != null)
        {
            Debug.LogError(value.DatabaseError.Message);
            return;
        }
        string JSON = value.Snapshot.GetRawJsonValue();
        Debug.Log(JSON);
        try
        {
            myRoom = JsonConvert.DeserializeObject<Room>(JSON);
        }
        catch (Exception ex)
        {
            Debug.LogException(ex);
        }


        switch (currentState)
        {
            case GameState.WaitingForOtherPlayersToJoin:
                UpdateConnectedPlayerList(myRoom.GetPlayerList());
                break;
            case GameState.MakeProposition:
                HostStartGame();
                break;
            default:
                break;
        }
    }

    /// <summary>
    /// Update the player labels on the WaitingForPlayer page
    /// </summary>
    /// <param name="_players"></param>
    private void UpdateConnectedPlayerList(List<Player> _players)
    {
        Debug.Log($"players count = {_players.Count}");
        for (int i = 0; i < _players.Count; i++)
        {
            Debug.Log($"player {i} = {_players[i].name}");
            waitingForPlayersLabels[i].text = _players[i].name;
        }
    }

    [ContextMenu("Fake Player Connection")]
    private void FakePlayerConnection()
    {
        Player temp = new Player("Momo");
        temp.id = Guid.NewGuid().ToString();
        temp.SetName("Momo");
    }

}
