using System;
using System.Collections;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;

namespace TwitchInGames {
    public class User {
        public string BroadcasterType { get; }
        public string Description { get; }
        public string DisplayName { get; }
        public string Email { get; }
        public string Id { get; }
        public string Login { get; }
        public string OfflineImageUrl { get; }
        public string ProfileImageUrl { get; }
        public string Type { get; }
        public uint ViewCount { get; }

        private const string _helixUrl = "https://api.twitch.tv/helix/";

        private User(HelixUser helixUser) {
            BroadcasterType = helixUser.broadcaster_type;
            Description = helixUser.description;
            DisplayName = helixUser.display_name;
            Email = helixUser.email;
            Id = helixUser.id;
            Login = helixUser.login;
            OfflineImageUrl = helixUser.offline_image_url;
            ProfileImageUrl = helixUser.profile_image_url;
            this.Type = helixUser.type;
            ViewCount = helixUser.view_count;
        }

        public static Coroutine<User> FetchCurrent(string clientId, string token) {
            return new Coroutine<User>(FetchUser(clientId, null, token));
        }

        public static Coroutine<User> FetchOtherById(string clientId, string id) {
            return new Coroutine<User>(FetchUser(clientId, "id", id));
        }

        public static Coroutine<User> FetchOtherByLogin(string clientId, string login) {
            login = login.ToLower();
            return new Coroutine<User>(FetchUser(clientId, "login", login));
        }

        private static IEnumerator FetchUser(string clientId, string type, string value) {
            // Validate the arguments.
            Utility.CheckArgument(nameof(clientId), clientId);
            Utility.CheckArgument(type ?? "token", value);

            // Create and start a Web request for the user.
            var url = type != null ? $"{_helixUrl}users?{type}={value}" : $"{_helixUrl}users";
            var www = UnityWebRequest.Get(url);
            www.SetRequestHeader("Client-ID", clientId);
            if(type == null) {
                // The value is the token.
                www.SetRequestHeader("Authorization", $"Bearer {value}");
            }
            yield return www.SendWebRequest();

            // Check for request failure.
            if(www.isNetworkError || www.isHttpError) {
                Debug.LogError(Utility.FormatMessage("User.FetchUser", www.error));
                throw new Exception(www.error);
            }

            // Extract the Helix response.
            var jsonEntity = JsonUtility.FromJson<HelixUserResponse>(www.downloadHandler.text);
            if(jsonEntity?.data == null || jsonEntity.data.Length < 1) {
                var message = $"Invalid JSON response for user {type ?? "token"} \"{value}\"";
                Debug.LogError(Utility.FormatMessage("User.FetchUser", message));
                throw new Exception(message);
            }

            // Convert the Helix user into an SDK user.
            var user = new User(jsonEntity.data[0]);
            yield return user;
        }

        public Coroutine<Texture2D> FetchOfflineImage() {
            return new Coroutine<Texture2D>(FetchImage(OfflineImageUrl));
        }

        public Coroutine<Texture2D> FetchProfileImage() {
            return new Coroutine<Texture2D>(FetchImage(ProfileImageUrl));
        }

        private IEnumerator FetchImage(string url) {
            // Validate the argument.
            Utility.CheckArgument(nameof(url), url);

            // Create and start a Web request for the image.
            var www = new WWW(url);
            yield return www;

            // Check for request failure.
            if(!String.IsNullOrEmpty(www.error)) {
                Debug.LogError(Utility.FormatMessage("User.FetchImage", www.error));
                throw new Exception(www.error);
            }

            // Provide 2D texture result.
            yield return www.texture;
        }

        public Coroutine<object> UpdateDescription(string clientId, string token, string description) {
            return new Coroutine<object>(InternalUpdateDescription(clientId, token, description));
        }

        public IEnumerator InternalUpdateDescription(string clientId, string token, string description) {
            // Validate the arguments.
            Utility.CheckArgument(nameof(clientId), clientId);
            Utility.CheckArgument(nameof(token), token);

            // Create and start a Web request to perform the update.
            var url = _helixUrl + "users?description=" + Uri.EscapeDataString(description);
            var www = UnityWebRequest.Put(url, default(byte[]));
            www.SetRequestHeader("Client-ID", clientId);
            www.SetRequestHeader("Authorization", $"Bearer {token}");
            yield return www.SendWebRequest();

            // Check for request failure.
            if(!String.IsNullOrEmpty(www.error)) {
                Debug.LogError(Utility.FormatMessage("User.UpdateDescription", www.error));
                throw new Exception(www.error);
            }
        }

        /// <summary>
        /// Contains User information from the Helix API.
        /// </summary>
        [Serializable]
        private class HelixUser {
#pragma warning disable 649 // Field '*' is never assigned to, and will always have its default value *
            public string broadcaster_type;   // User's broadcaster type: "partner", "affiliate", or "".
            public string description;        // User's channel description.
            public string display_name;       // User's display name.
            public string email;              // User's email address. Returned if the request includes the user:read:edit scope.
            public string id;                 // User's ID.
            public string login;              // User's login name.
            public string offline_image_url;  // URL of the user's offline image.
            public string profile_image_url;  // URL of the user's profile image.
            public string type;               // User's type: "staff", "admin", "global_mod", or "".
            public uint view_count;           // Number of users following this user.
#pragma warning restore 649
        }

        [Serializable]
        private class HelixUserResponse {
#pragma warning disable 649 // Field 'data' is never assigned to, and will always have its default value null
            public HelixUser[] data;
#pragma warning restore 649
        }
    }
}
