using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;
using Newtonsoft.Json;
using UnityEngine;

namespace Assets.Scripts.Fitbit
{
  
    public class FitBitAPI : MonoBehaviour
    {
        
        private const string _consumerSecret = "69307b9f332caf9946ef4e23cabde2e4";
        private const string _clientId = "22CX4L";
        private const string _callbackURL = "http://localhost/callback";
        private const string CustomAndroidScheme = "mocha://";

        private const string _tokenUrl = "https://api.fitbit.com/oauth2/token";
        private const string _baseGetUrl = "https://api.fitbit.com/1/user/-/";
        private const string _profileUrl = _baseGetUrl + "profile.json/";
        private const string _activityUrl = _baseGetUrl + "activities/";

        private string _distanceUrl = _activityUrl + "distance/date/" + _currentDateTime + "/1d.json";

        private string _stepsUrl = _activityUrl + "steps/date/" + _currentDateTime + "/1d.json";

        private string _caloriesUrl = _activityUrl + "calories/date/" + _currentDateTime + "/1d.json";

        private string _sleepUrl = _baseGetUrl + "sleep/minutesAsleep/date/" + _currentDateTime + "/" + _currentDateTime + ".json";

        private static string _currentDateTime = GetCurrentDate();

        private string _returnCode;
        private WWW _wwwRequest;
        private bool _bGotTheData = false;
        private bool _bFirstFire = true;

        private OAuth2AccessToken _oAuth2 = new OAuth2AccessToken();
        public FitbitData _fitbitData = new FitbitData();
        
        private string _statusMessage;

        private string CallBackUrl
        {
            get
            {
                //determine which platform we're running on and use the appropriate url
                if (Application.platform == RuntimePlatform.WindowsEditor)
                    return WWW.EscapeURL(_callbackURL);
                else if (Application.platform == RuntimePlatform.Android)
                {
                    return WWW.EscapeURL(CustomAndroidScheme);
                }
                else
                {
                    return WWW.EscapeURL(CustomAndroidScheme);
                }
            }
        }

        public void Start()
        {
            DontDestroyOnLoad(this);
        }
        private void OnGUI()
        {
            if (!_bGotTheData && !string.IsNullOrEmpty(_statusMessage) && _bFirstFire)
            {
                _bFirstFire = false;
            }
        }

        public void LoginToFitbit()
        {

            if (PlayerPrefs.HasKey("FitbitRefreshToken"))
            {
                UseRefreshToken();
            }
            else
            {
                UserAcceptOrDeny();
            }

        }
        public void UserAcceptOrDeny()
        {
            var url =
                "https://www.fitbit.com/oauth2/authorize?response_type=code&client_id=" + _clientId + "&prompt=login&redirect_uri=" +
                CallBackUrl +
                "&scope=activity%20nutrition%20heartrate%20location%20profile%20sleep%20weight%20social";
            Application.OpenURL(url);
            // print(url);
#if UNITY_EDITOR
#endif
        }

        public void ClearRefreshCode()
        {
            PlayerPrefs.DeleteKey("FitbitRefreshToken");
            Debug.Log("Refresh Token has been CLEARED!");
        }

        private void UseReturnCode()
        {
            Debug.Log("return code isn't empty");
            var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(_clientId + ":" + _consumerSecret);
            var encoded = Convert.ToBase64String(plainTextBytes);

            var form = new WWWForm();
            form.AddField("client_id", _clientId);
            form.AddField("grant_type", "authorization_code");
            form.AddField("redirect_uri", WWW.UnEscapeURL(CallBackUrl));
            form.AddField("code", _returnCode);

            var headers = form.headers;
            headers["Authorization"] = "Basic " + encoded;

            _wwwRequest = new WWW(_tokenUrl, form.data, headers);
            StartCoroutine(WaitForAccess(_wwwRequest));
            
            while (!_wwwRequest.isDone)
            {
            }

            Debug.Log("Token: " + _wwwRequest.text);
            Debug.Log("parsing token");

            var parsed = new JSONObject(_wwwRequest.text);
            ParseAccessToken(parsed);
            Debug.Log("\nParsed Token: " + _oAuth2.Token);
            GetAllData();
            Debug.Log("Steps from Fitbit: " + _fitbitData.CurrentSteps);
            _bGotTheData = true;
        }

        public void UseRefreshToken()
        {
            Debug.Log("Using Refresh Token");
            var plainTextBytes = Encoding.UTF8.GetBytes(_clientId + ":" + _consumerSecret);
            var encoded = Convert.ToBase64String(plainTextBytes);

            var form = new WWWForm();
            form.AddField("grant_type", "refresh_token");
            form.AddField("refresh_token", PlayerPrefs.GetString("FitbitRefreshToken"));

            var headers = form.headers;
            headers["Authorization"] = "Basic " + encoded;

            _wwwRequest = new WWW(_tokenUrl, form.data, headers);
            StartCoroutine(WaitForAccess(_wwwRequest));
            while (!_wwwRequest.isDone)
            {
            }

            Debug.Log("RefreshToken wwwText: " + _wwwRequest.text);
            if (!String.IsNullOrEmpty(_wwwRequest.error))
            {
                PlayerPrefs.DeleteKey("FitbitRefreshToken");
                UserAcceptOrDeny();
                UseReturnCode();
                GetAllData();
            }
            else
            {
                Debug.Log("Using the Auth Token (UseRefreshToken)");
                var parsed = new JSONObject(_wwwRequest.text);
                ParseAccessToken(parsed);
                GetAllData();
            }
        }

        public void SetReturnCodeFromAndroid(string code)
        {
            if (string.IsNullOrEmpty(code))
                return;
            _returnCode = code.Substring(CustomAndroidScheme.Length + 6);
            Debug.Log("Return Code is: " + _returnCode);
            UseReturnCode();
        }

        public void SetReturnCode(string code)
        {
            if (string.IsNullOrEmpty(code))
                return;

            _returnCode = code;
            UseReturnCode();
        }
        
        public void GetAllData()
        {
            GetProfileData();
            GetAllRelevantData();
            BuildProfile();
            _fitbitData.LastSyncTime = DateTime.Now.ToUniversalTime();
            Debug.Log("LastSyncTime: " + DateTime.Now.ToUniversalTime().ToString("g"));
        }

        private void GetAllRelevantData()
        {
            GetSteps();
            GetDistance();
            GetCalories();
            GetSleep();
        }

        #region GetData
        private void GetProfileData()
        {

            //time for Getting Dataz
            var headers = new Dictionary<string, string>();
            headers["Authorization"] = "Bearer " + _oAuth2.Token;

            _wwwRequest = new WWW(_profileUrl, null, headers);
            Debug.Log("Doing GET Request");
            StartCoroutine(WaitForAccess(_wwwRequest));
            
            while (!_wwwRequest.isDone)
            {
            }

            ParseProfileData(_wwwRequest.text);

        }

        private void GetCalories()
        {
            var headers = new Dictionary<string, string>();
            headers["Authorization"] = "Bearer " + _oAuth2.Token;

            Debug.Log("Calories URL is: " + _caloriesUrl);
            _wwwRequest = new WWW(_caloriesUrl, null, headers);
            Debug.Log("Doing Calories GET Request");
            StartCoroutine(WaitForAccess(_wwwRequest));
            
            while (!_wwwRequest.isDone)
            {
            }
            ParseCaloriesData(_wwwRequest.text);
        }

        private void GetDistance()
        {
            var headers = new Dictionary<string, string>();
            headers["Authorization"] = "Bearer " + _oAuth2.Token;

            Debug.Log("Distance URL is: " + _distanceUrl);
            _wwwRequest = new WWW(_distanceUrl, null, headers);
            Debug.Log("Doing Distance GET Request");
            StartCoroutine(WaitForAccess(_wwwRequest));
            
            while (!_wwwRequest.isDone)
            {
            }

            ParseDistanceData(_wwwRequest.text);
        }

        private void GetSteps()
        {
            var headers = new Dictionary<string, string>();
            headers["Authorization"] = "Bearer " + _oAuth2.Token;

            Debug.Log("Steps URL is: " + _stepsUrl);
            _wwwRequest = new WWW(_stepsUrl, null, headers);
            Debug.Log("Doing Steps GET Request");
            StartCoroutine(WaitForAccess(_wwwRequest));
            
            while (!_wwwRequest.isDone)
            {
            }

            ParseStepsData(_wwwRequest.text);
        }

        private void GetSleep()
        {
            var headers = new Dictionary<string, string>();
            headers["Authorization"] = "Bearer " + _oAuth2.Token;

            Debug.Log("Sleep URL is: " + _sleepUrl);
            _wwwRequest = new WWW(_sleepUrl, null, headers);
            Debug.Log("Doing Sleep GET Request");
            StartCoroutine(WaitForAccess(_wwwRequest));

            //DIRTY DIRTY HACK
            while (!_wwwRequest.isDone)
            {
            }

            ParseSleepData(_wwwRequest.text);
        }

        private void BuildProfile()
        {
            var imageWWW = new WWW(_fitbitData.ProfileData["avatar"]);
            //DIRTY DIRTY HACK
            while (!imageWWW.isDone)
            {
            }

            Debug.Log(_fitbitData.RawProfileData["fullName"]);

            //we should check to see if there is "data" already
            if (_fitbitData.ProfileData.Count != 0)
            {
                foreach (KeyValuePair<string, string> kvp in _fitbitData.ProfileData)
                {
                    if (kvp.Key == "avatar")
                        continue;
                    
                    //put a space between the camelCase
                    var tempKey = Regex.Replace(kvp.Key, "(\\B[A-Z])", " $1");
                    //then capitalize the first letter
                    UppercaseFirst(tempKey);
                }
            }
            _bGotTheData = true;
        }
        #endregion

        #region Parsing
        private void ParseAccessToken(JSONObject parsed)
        {
            var dict = parsed.ToDictionary();
            foreach (KeyValuePair<string, string> kvp in dict)
            {
                if (kvp.Key == "access_token")
                {
                    _oAuth2.Token = kvp.Value;
                    PlayerPrefs.SetString("FitbitAccessToken", kvp.Value);
                }
                else if (kvp.Key == "expires_in")
                {
                    var num = 0;
                    Int32.TryParse(kvp.Value, out num);
                    _oAuth2.ExpiresIn = num;

                }
                else if (kvp.Key == "refresh_token")
                {
                    _oAuth2.RefreshToken = kvp.Value;
                    Debug.Log("REFRESH TOKEN: " + kvp.Value);
                    PlayerPrefs.SetString("FitbitRefreshToken", kvp.Value);
                    Debug.Log("Token We Just Store: " + PlayerPrefs.GetString("FitbitRefreshToken"));
                }
                else if (kvp.Key == "token_type")
                {
                    _oAuth2.TokenType = kvp.Value;
                    PlayerPrefs.SetString("FitbitTokenType", kvp.Value);
                }
            }
        }

        private void ParseProfileData(string data)
        {
            Debug.Log("inserting json data into fitbitData.RawProfileData");
            //Debug.LogWarning(data);
            XmlDocument xmldoc = JsonConvert.DeserializeXmlNode(data);

            var doc = XDocument.Parse(xmldoc.InnerXml);


            doc.Descendants("topBadges").Remove();
            foreach (XElement xElement in doc.Descendants())
            {
                //Debug.Log(xElement.Name.LocalName + ": Value:" + xElement.Value);V
                if (!_fitbitData.RawProfileData.ContainsKey(xElement.Name.LocalName))
                    _fitbitData.RawProfileData.Add(xElement.Name.LocalName, xElement.Value);
                else
                {
                    //Debug.LogWarning("Key already found in RawProfileData: " + xElement.Name.LocalName);
                    //if the key is already in the dict, we will just update the value for consistency.
                    _fitbitData.RawProfileData[xElement.Name.LocalName] = xElement.Value;
                }

                if (_fitbitData.ProfileData.ContainsKey(xElement.Name.LocalName))
                {
                    _fitbitData.ProfileData[xElement.Name.LocalName] = xElement.Value;
                }
            }
        }

        private void ParseStepsData(string data)
        {
            //convert the json to xml cause json blows hard.
            XmlDocument json = JsonConvert.DeserializeXmlNode(data);

            XDocument doc = XDocument.Parse(json.InnerXml);
            var root = doc.Descendants("value").FirstOrDefault();
            _fitbitData.CurrentSteps = ToInt(root.Value);
            Debug.Log("Steps from Fitbit: " + _fitbitData.CurrentSteps);
        }

        private void ParseDistanceData(string data)
        {
            XmlDocument json = JsonConvert.DeserializeXmlNode(data);

            XDocument doc = XDocument.Parse(json.InnerXml);
            var root = doc.Descendants("value").FirstOrDefault().Value;
            //trim the value
            if (root.Length > 4)
                root = root.Substring(0, 4);

            _fitbitData.CurrentDistance = ToDouble(root);

            Debug.Log("Distance from Fitbit is:" + _fitbitData.CurrentDistance);
        }

        private void ParseCaloriesData(string data)
        {
            XmlDocument json = JsonConvert.DeserializeXmlNode(data);

            var doc = XDocument.Parse(json.InnerXml);
            var calories = doc.Descendants("value").FirstOrDefault().Value;

            _fitbitData.CurrentCalories = ToInt(calories);
        }

        private void ParseSleepData(string data)
        {
            Debug.Log(data);
            XmlDocument json = JsonConvert.DeserializeXmlNode(data);

            var doc = XDocument.Parse(json.InnerXml);
            var sleepTimeTotal = doc.Descendants("value").FirstOrDefault().Value;
            Debug.Log("Minutes asleep for: " + sleepTimeTotal);

            _fitbitData.CurrentSleep = ToInt(sleepTimeTotal);
        }


        #endregion

        static string UppercaseFirst(string s)
        {
            // Check for empty string.
            if (string.IsNullOrEmpty(s))
            {
                return string.Empty;
            }
            // Return char and concat substring.
            return char.ToUpper(s[0]) + s.Substring(1);
        }

        IEnumerator WaitForAccess(WWW www)
        {
            Debug.Log("waiting for access\n");
            yield return www;
            Debug.Log("Past the Yield \n");
            // check for errors
            if (www.error == null)
            {
                Debug.Log("no error \n");
                Debug.Log("Steps from Fitbit: " + _fitbitData.CurrentSteps);
                Debug.Log(www.text);
                Debug.Log("Steps from Fitbit: " + _fitbitData.CurrentSteps);
            }
            if (www.error != null)
            {
                Debug.Log("\n Error" + www.error);
                Debug.Log(www.error);
            }
            Debug.Log("end of WaitForAccess \n");
        }

        //just a utility function to get the correct date format for activity calls that require one
        public static string GetCurrentDate()
        {
            var date = "";
            date += DateTime.Now.Year;
            if (DateTime.Now.Month < 10)
            {
                date += "-" + "0" + DateTime.Now.Month;
            }
            else
            {
                date += "-" + DateTime.Now.Month;
            }

            if (DateTime.Now.Day < 10)
            {
                date += "-" + "0" + DateTime.Now.Day;
            }
            else
            {
                date += "-" + DateTime.Now.Day;
            }
            //date += "-" + 15;
            return date;
        }

        private int ToInt(string thing)
        {
            var temp = 0;
            Int32.TryParse(thing, out temp);
            return temp;
        }

        private double ToDouble(string thing)
        {
            var temp = 0.0;
            Double.TryParse(thing, out temp);
            return temp;
        }

    }

}
