using System;
using System.Collections.Generic;
using System.Globalization;
using UnityEngine;

public class LocalPlayerData
{
    /// <summary>
    ///     last known email used
    /// </summary>
    public string LastKnownEmail = "";
    
    /// <summary>
    ///     last known username used
    /// </summary>
    public string LastKnownUsername = "Guest";
    
    /// <summary>
    ///     queue of the 10 most recent local scores
    /// </summary>
    public Queue<Score> RecentLocalScores = new(10);
    
    /// <summary>
    ///     queue of the 10 most recent online scores,
    ///     used in user rating calculation and accuracy display stats
    /// </summary>
    public Queue<Score> RecentOnlineScores = new(10);
    
    /// <summary>
    ///     queue of the best online scores,
    ///     used in user rating calculation and accuracy display stats
    /// </summary>
    public Queue<Score> BestOnlineScores = new(30);

    /// <summary>
    ///     loads player data from player prefs and database
    /// </summary>
    public void LoadFromTheWorld()
    {
        // load user data, possibly from the backend
        var possibleUser = GameManager.Instance.Backend.GetUser();
        var currentKnownEmail = string.Empty;
        var currentKnownUsername = string.Empty;
        if (possibleUser != null)
        {
            currentKnownEmail = possibleUser.Email;
            currentKnownUsername = GameManager.Instance.Backend.GetUsername();
        }
        var lastStoredEmail = PlayerPrefs.GetString("LastKnownEmail", "");
        var lastStoredUsername = PlayerPrefs.GetString("LastKnownUsername", "Guest");
        LastKnownEmail = string.IsNullOrEmpty(currentKnownEmail) ? lastStoredEmail : currentKnownEmail;
        LastKnownUsername = string.IsNullOrEmpty(currentKnownUsername) ? lastStoredUsername : currentKnownUsername;

        // load local scores
        RecentLocalScores.Clear();
        for (var idx = 0; idx < 10; idx++)
        {
            var timestampRaw = PlayerPrefs.GetString($"RecentLocalScores_{idx}_Timestamp", "");
            if (timestampRaw == "") continue;

            var timestamp = DateTime.TryParseExact(timestampRaw,
                "s",
                CultureInfo.InvariantCulture,
                DateTimeStyles.None,
                out var t)
                ? t
                : DateTime.MinValue;

            if (timestamp == DateTime.MinValue) continue;

            var noOfRounds = PlayerPrefs.GetInt($"RecentLocalScores_{idx}_NoOfRounds", -1);
            var l = PlayerPrefs.GetFloat($"RecentLocalScores_{idx}_AvgLightnessAccuracy", -1f);
            var c = PlayerPrefs.GetFloat($"RecentLocalScores_{idx}_AvgChromaAccuracy", -1f);
            var h = PlayerPrefs.GetFloat($"RecentLocalScores_{idx}_AvgHueAccuracy", -1f);

            // if any of the values are invalid, don't add the score
            if (noOfRounds < 0 || l < 0 || c < 0 || h < 0) continue;

            RegisterLocalScore(new Score(timestamp, noOfRounds, l, c, h));
        }
        
        // load online scores
        RecentOnlineScores.Clear();
        GameManager.Instance.Backend.GetRecentScores((dtr, recentOnlineScores) =>
        {
            foreach (var onlineScore in recentOnlineScores)
            {
                if (RecentOnlineScores.Count > 10) RecentOnlineScores.Dequeue();
                RecentOnlineScores.Enqueue(onlineScore);
            }
        });
    }

    /// <summary>
    ///     registers a score to the player's local data
    /// </summary>
    /// <param name="score">the score to register</param>
    public void RegisterLocalScore(Score score)
    {
        if (RecentLocalScores.Count > 10) RecentLocalScores.Dequeue();
        RecentLocalScores.Enqueue(score);
    }

    /// <summary>
    ///     saves player data to player prefs
    /// </summary>
    public void SaveToTheWorld()
    {
        PlayerPrefs.SetString("LastKnownEmail", LastKnownEmail);
        PlayerPrefs.SetString("LastKnownUsername", LastKnownUsername);

        var idx = 0;
        foreach (var score in RecentLocalScores)
        {
            PlayerPrefs.SetString($"RecentLocalScores_{idx}_Timestamp",
                score.Timestamp.ToString("s", CultureInfo.InvariantCulture));
            PlayerPrefs.SetInt($"RecentLocalScores_{idx}_NoOfRounds", score.NoOfRounds);
            PlayerPrefs.SetFloat($"RecentLocalScores_{idx}_AvgLightnessAccuracy", score.AvgLightnessAccuracy);
            PlayerPrefs.SetFloat($"RecentLocalScores_{idx}_AvgChromaAccuracy", score.AvgChromaAccuracy);
            PlayerPrefs.SetFloat($"RecentLocalScores_{idx}_AvgHueAccuracy", score.AvgHueAccuracy);
            idx++;
        }

        // online scores are already saved in the backend
    }

    public struct Score
    {
        /// <summary>
        ///     timestamp of the score
        /// </summary>
        public DateTime Timestamp;

        /// <summary>
        ///     number of rounds played (0-100)
        /// </summary>
        public int NoOfRounds;

        /// <summary>
        ///     average lightness accuracy across all rounds (0-100)
        /// </summary>
        public float AvgLightnessAccuracy;

        /// <summary>
        ///     average chroma accuracy across all rounds (0-100)
        /// </summary>
        public float AvgChromaAccuracy;

        /// <summary>
        ///     average hue accuracy across all rounds (0-100)
        /// </summary>
        public float AvgHueAccuracy;

        /// <summary>
        ///     constructor for the score struct
        /// </summary>
        /// <param name="timestamp">timestamp of the score</param>
        /// <param name="noOfRounds">number of rounds played (0-100)</param>
        /// <param name="l">average lightness accuracy across all rounds (0-100)</param>
        /// <param name="c">average chroma accuracy across all rounds (0-100)</param>
        /// <param name="h">average hue accuracy across all rounds (0-100)</param>
        public Score(DateTime timestamp = new(), int noOfRounds = 1, float l = 100.0f, float c = 100.0f,
            float h = 100.0f)
        {
            Timestamp = timestamp;
            NoOfRounds = noOfRounds;
            AvgLightnessAccuracy = l;
            AvgChromaAccuracy = c;
            AvgHueAccuracy = h;
        }
    }
}