using System;
using System.Collections.Generic;
using System.Linq;
using Firebase.Database;
using TMPro;
using UnityEngine;

public class VotingPage : MonoBehaviour
{
    [Header("Scene References")]
    public PropositionFrame proposition1;
    public PropositionFrame proposition2;
    public TextMeshProUGUI currentPromptLabel;
    public TextMeshProUGUI timerLabel;

    [Header("Asset References")]
    public PromptList promptList;

    [Header("Settings")]
    public float votingTime = 20;

    private Queue<Question> remainingQuestions = new Queue<Question>();
    private Question currentQuestion = null;

    private DatabaseReference roomRef;
    private Dictionary<string, Player> currentPlayers; // All the current playing players

    private DateTime endOfTimer = DateTime.MinValue;

    private Action OnVoteEnded;

    private bool allPlayersHaveVotedForCurrentQuestion = false;

    /// <summary>
    /// Is the number of questions for this game sessions
    /// </summary>
    private int numberOfQuestions = 0;

    private void Update()
    {
        // If there is no question displayed or the timer is not started, return early.
        if (currentQuestion == null || endOfTimer == DateTime.MinValue)
            return;

        if (DateTime.Now < endOfTimer)
        {
            TimeSpan duration = endOfTimer - DateTime.Now;
            timerLabel.text = ((int)duration.TotalSeconds).ToString("D2");

            if (allPlayersHaveVotedForCurrentQuestion && endOfTimer > DateTime.Now.AddSeconds(4))
            {
                Debug.Log("All players have voted. So I put only 4sec to look at the winner.", this);
                endOfTimer = DateTime.Now.AddSeconds(4);
            }
        }
        else
        {
            EndCurrentQuestion();
        }
    }

    /// <summary>
    /// End current question and timer and go to next question (or end the vote if there is no more questions)
    /// </summary>
    private void EndCurrentQuestion()
    {
        timerLabel.text = "0";
        if (remainingQuestions.Count > 0)
        {
            currentQuestion = remainingQuestions.Dequeue();
            ShowNextQuestion();
        }
        else
        {
            OnVoteEnded?.Invoke();
            OnVoteEnded = null;
            gameObject.SetActive(false);

            for (int i = 0; i < numberOfQuestions; i++)
            {
                roomRef.Child("questions").Child(i.ToString()).ValueChanged -= OnQuestionDataUpdate;
            }
        }
    }

    /// <summary>
    /// Initializes the voting page with necessary data, such as the room reference, current players,
    /// questions, and a callback function for when the voting ends.
    /// </summary>
    /// <param name="_roomRef">Reference to the Firebase room database.</param>
    /// <param name="_currentPlayers">Dictionary of current players.</param>
    /// <param name="_questions">Dictionary of questions associated with their IDs.</param>
    /// <param name="_callback_OnVoteEnded">Callback function to invoke when the voting ends.</param>
    public void ShowVotingPage(DatabaseReference _roomRef, Dictionary<string, Player> _currentPlayers, Dictionary<int, Question> _questions, Action _callback_OnVoteEnded)
    {
        Debug.Log("Initializing voting page");
        roomRef = _roomRef;
        currentPlayers = _currentPlayers;
        OnVoteEnded = _callback_OnVoteEnded;
        allPlayersHaveVotedForCurrentQuestion = false;
        endOfTimer = DateTime.MinValue;
        remainingQuestions.Clear();
        currentQuestion = null;

        Question[] _questionArray = _questions.Values.ToArray();
        numberOfQuestions = _questionArray.Length;

        for (int i = 0; i < _questionArray.Length; i++)
        {
            remainingQuestions.Enqueue(_questionArray[i]);
            roomRef.Child("questions").Child(i.ToString()).ValueChanged += OnQuestionDataUpdate;
        }

        gameObject.SetActive(true);
        currentQuestion = remainingQuestions.Dequeue();
        ShowNextQuestion();
    }

    /// <summary>
    /// Displays the next question on the screen, updating the prompt, timer, and proposition frames.
    /// </summary>
    private void ShowNextQuestion()
    {
        // Set the current question ID in the Firebase room database.
        roomRef.Child("currentQuestionId").SetValueAsync(currentQuestion.index);

        // Update the prompt text based on the current question's prompt ID.
        Debug.Log($"Show Question {currentQuestion.promptId}", this);
        Prompt currentPrompt = promptList.GetPromptById(currentQuestion.promptId);
        currentPromptLabel.text = RoomManager.Instance.myRoom.promptsLanguage == "en" ? currentPrompt.en : currentPrompt.fr;

        Debug.Log($"Prompt is {currentPromptLabel.text}", this);

        // Initialize the proposition frames with proposition data.
        InitializeProposition(proposition1, currentQuestion.propositions[0]);
        InitializeProposition(proposition2, currentQuestion.propositions[1]);

        // Set the timer to end after the specified voting time.
        endOfTimer = DateTime.Now.AddSeconds(votingTime);

        //reset the count of voters
        allPlayersHaveVotedForCurrentQuestion = false;
    }

    /// <summary>
    /// Initializes a proposition frame with the provided proposition data, updating it if available,
    /// or handling the case when there is no proposition.
    /// </summary>
    /// <param name="proposition">The PropositionFrame to initialize or update.</param>
    /// <param name="propositionData">The Proposition data to use for initialization or update.</param>
    private void InitializeProposition(PropositionFrame proposition, Proposition propositionData)
    {
        if (propositionData != null)
        {
            proposition.SetProposition(propositionData);
        }
        else
        {
            Debug.Log("User has given no proposition", this);
            proposition.SetNoProposition();
        }
    }

    /// <summary>
    /// Handles updates to a question, such as when someone has voted for it.
    /// </summary>
    /// <param name="sender">The sender of the event.</param>
    /// <param name="_questionRef">The ValueChangedEventArgs containing the updated question reference.</param>
    private void OnQuestionDataUpdate(object sender, ValueChangedEventArgs _questionRef)
    {
        string JSON = _questionRef.Snapshot.GetRawJsonValue();

        if (string.IsNullOrEmpty(JSON))
            return;

        Question question = Newtonsoft.Json.JsonConvert.DeserializeObject<Question>(JSON);

        // If someone succeeded in voting for another question than the one displayed, ignore it.
        if (question.index != currentQuestion.index)
            return;

        currentQuestion.propositions = question.propositions;

        if (currentQuestion.propositions.Count > 0)
        {
            // Update the proposition voters for both propositions.
            UpdatePropositionVoters(proposition1, currentQuestion.propositions[0]);
            UpdatePropositionVoters(proposition2, currentQuestion.propositions[1]);
        }

        //every players BUT the one who have created the propositions
        if (RoomManager.Instance.myRoom.NumbersOfVotersForQuestion(currentQuestion) >= currentPlayers.Count - 2)
        {
            allPlayersHaveVotedForCurrentQuestion = true;
        }

    }

    /// <summary>
    /// Updates the voters for a specific proposition frame based on the provided proposition data.
    /// If the proposition data is not null, it retrieves the list of voters and updates the frame.
    /// If there is no proposition data, it logs a message indicating that the user has not given a proposition.
    /// </summary>
    /// <param name="proposition">The PropositionFrame to update.</param>
    /// <param name="propositionData">The Proposition data used to update voters for the proposition frame.</param>
    private void UpdatePropositionVoters(PropositionFrame proposition, Proposition propositionData)
    {
        if (propositionData != null)
        {
            List<Player> voters = GetVotersForProposition(propositionData);
            proposition.UpdateVoters(voters);
        }
        else
        {
            Debug.Log("User has given no proposition", this);
            // Handle the case when there is no proposition
        }
    }

    /// <summary>
    /// Retrieves the list of players who have voted for a specific proposition based on the provided proposition data.
    /// It checks if the proposition data contains a list of voters and then looks up player information in the current players dictionary.
    /// </summary>
    /// <param name="propositionData">The Proposition data for which to retrieve voters.</param>
    /// <returns>The list of players who voted for the proposition.</returns>
    private List<Player> GetVotersForProposition(Proposition propositionData)
    {
        List<Player> voters = new List<Player>();

        if (propositionData.voters != null)
        {
            foreach (string playerId in propositionData.voters)
            {
                voters.Add(currentPlayers[playerId]);
                Debug.Log($"updating number of voters for: {propositionData.owner.name}'s propositions. They are now {voters.Count}.");
            }
        }

        return voters;
    }
}

