using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using Newtonsoft.Json;
using Windows.Storage;
using Windows.Services.Store;
using System.Net.Http;
using Google.Apis.Oauth2.v2.Data;
using Google.Apis.Oauth2.v2;
using static Google.Apis.Auth.OAuth2.UwpCodeReceiver;
using Microsoft.AppCenter.Analytics;
using System.Net;

namespace FoxTube
{
    public static class SecretsVault
    {
        #region Properties
        //Events
        public static event ObjectEventHandler AuthorizationStateChanged;
        public static event ObjectEventHandler SubscriptionsChanged;
        public static event ObjectEventHandler Purchased; //Rising when app finds out that it's not a PRO version

        //Properties
        public static NetworkCredential EmailCredential => new NetworkCredential("mikhailagord@gmail.com", "JkY39w$.7?bT57O,8k3a");
        private static ClientSecrets Secrets => new ClientSecrets
        {
            ClientId = "349735264870-2ekqlm0a4mkg3mmrfcv90s3qp3o15dq0.apps.googleusercontent.com",
            ClientSecret = "BkVZOAaCU2Zclf0Zlicg6y2_"
        };
        private static YouTubeService NoAuthService => new YouTubeService(new BaseClientService.Initializer
        {
            ApiKey = "AIzaSyBgHrCnrlzlVmk0cJKL8RqP9Y8x6XSuk_0",
            ApplicationName = "FoxTube"
        });
        public static BaseClientService.Initializer Initializer => new BaseClientService.Initializer
        {
            HttpClientInitializer = Credential,
            ApplicationName = "FoxTube"
        };
        public static YouTubeService Service => IsAuthorized ? new YouTubeService(Initializer) : NoAuthService;

        public static HttpClient HttpClient { get; } = new HttpClient();
        private static bool TestAds => false;   //TODO: Change this bool
        public static string AppId => TestAds ? "d25517cb-12d4-4699-8bdc-52040c712cab" : "9ncqqxjtdlfh";
        public static string AdUnitId => TestAds ? "test" : "1100037769";
        public static bool AdsDisabled { get; private set; } = true;

        //User info
        public static bool IsAuthorized => Credential != null;
        private static UserCredential Credential { get; set; } = null;

        public static string AccountId => UserChannel?.Id;
        public static Channel UserChannel { get; private set; }
        public static Userinfoplus UserInfo { get; private set; }

        public static List<Subscription> Subscriptions { get; } = new List<Subscription>();
        public static List<string> History { get; set; } = new List<string>();
        public static List<string> WatchLater { get; set; } = new List<string>();
        #endregion

        #region Methods
        public static void RefreshToken()
        {
            Credential?.RefreshTokenAsync(CancellationToken.None);
        }

        /// <summary>
        /// Subscribes or unsibscribes authorized user from the channel
        /// </summary>
        /// <param name="id">The ID of channel which has to be added/removed</param>
        /// <returns>Returns 'true' if channel is in subscriptions now; 'false' if it's not</returns>
        public static async Task<bool> ChangeSubscriptionState(string id)
        {
            if(Subscriptions.Exists(x => x.Snippet.ResourceId.ChannelId == id))
            {
                Subscription s = Subscriptions.Find(x => x.Snippet.ResourceId.ChannelId == id);

                try { await Service.Subscriptions.Delete(s.Id).ExecuteAsync(); }
                catch { return true; }

                SubscriptionsChanged?.Invoke(null, "remove", s);
                Subscriptions.Remove(s);

                SaveSubscriptions();
                return false;
            }
            else
            {
                var request = Service.Subscriptions.Insert(new Subscription()
                {
                    Snippet = new SubscriptionSnippet()
                    {
                        ResourceId = new ResourceId()
                        {
                            ChannelId = id,
                            Kind = "youtube#channel"
                        }
                    }
                }, "snippet");

                Subscription s = await request.ExecuteAsync();
                if (s == null)
                    return false;
                Subscriptions.Add(s);
                SubscriptionsChanged?.Invoke(null, "add", s);

                SaveSubscriptions();
                return true;
            }
        }

        /// <summary>
        /// Sets up **SecretsVault**
        /// </summary>
        public static void Initialize()
        {
            CheckAddons();
            CheckAuthorization();
        }

        /// <summary>
        /// Prompts to add an Youtube account and retrieves its info when successful
        /// </summary>
        /// <param name="retrieveSubs">Loads user's subscriptions if true</param>
        public static async void Authorize(bool retrieveSubs = true)
        {
            #region Retrieving user's credential
            try
            {
                Credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
                    Secrets,
                    new[]
                    {
                            Oauth2Service.Scope.UserinfoProfile,
                            Oauth2Service.Scope.UserinfoEmail,
                            YouTubeService.Scope.YoutubeForceSsl,
                            YouTubeService.Scope.Youtube,
                            YouTubeService.Scope.YoutubeUpload,
                            YouTubeService.Scope.YoutubeReadonly,
                            YouTubeService.Scope.Youtubepartner
                    },
                    "user",
                    CancellationToken.None);

                await Credential.RefreshTokenAsync(CancellationToken.None);
            }
            catch (AuthenticateException e)
            {
                if (e.Message.Contains("UserCancel"))
                    return;
                else
                {
                    AuthorizationStateChanged?.Invoke(args: new bool?[] { null });
                    Analytics.TrackEvent("Failed to authorize", new Dictionary<string, string>
                    {
                        { "Exception", e.GetType().ToString() },
                        { "Message", e.Message },
                        { "StackTrace", e.StackTrace }
                    });
                }
            }

            if (Credential == null || !retrieveSubs)
                return;

            HttpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Credential.Token.AccessToken);

            SettingsStorage.HasAccount = true;
            #endregion

            try
            {
                #region Retrieving user's data
                UserInfo = await new Oauth2Service(Initializer).Userinfo.Get().ExecuteAsync();

                WatchLater = await Methods.GetLater();
                History = await Methods.GetHistory();

                SubscriptionsResource.ListRequest subRequest = Service.Subscriptions.List("snippet");
                subRequest.Mine = true;
                subRequest.MaxResults = 50;
                subRequest.Order = SubscriptionsResource.ListRequest.OrderEnum.Relevance;
                SubscriptionListResponse subResponse;
                string nextToken = null;
                Subscriptions.Clear();

                do
                {
                    subRequest.PageToken = nextToken;
                    subResponse = await subRequest.ExecuteAsync();
                    foreach (Subscription s in subResponse.Items)
                        Subscriptions.Add(s);
                    nextToken = subResponse.NextPageToken;

                } while (!string.IsNullOrWhiteSpace(nextToken));

                var request = Service.Channels.List("snippet,contentDetails");
                request.Mine = true;
                UserChannel = (await request.ExecuteAsync()).Items[0];
                #endregion

                //Saving user's subscriptions for background task
                SaveSubscriptions();

                AuthorizationStateChanged?.Invoke(args: true);
            }
            catch (Exception e)
            {
                AuthorizationStateChanged?.Invoke(args: new bool?[] { null });
                Methods.SendMail($@"Exception: {e.GetType()}
Message: {e.Message}
Stack trace: {e.StackTrace}");
                Analytics.TrackEvent("Failed to retrieve user's info", new Dictionary<string, string>
                {
                    { "Exception", e.GetType().ToString() },
                    { "Message", e.Message },
                    { "StackTrace", e.StackTrace }
                });
            }
        }

        /// <summary>
        /// Saves user's subscriptions keypairs (channel ID: avatar URL) into "background.json" file for concurrent background processing
        /// </summary>
        public static void SaveSubscriptions()
        {
            try
            {
                Dictionary<string, string> subs = new Dictionary<string, string>();
                foreach (Subscription i in Subscriptions)
                    try
                    {
                        subs.Add(i.Snippet.ResourceId.ChannelId, i.Snippet.Thumbnails.Default__.Url);
                    }
                    catch (Exception e)
                    {
                        Analytics.TrackEvent("Failed to save user's subscription", new Dictionary<string, string>
                    {
                        { "Exception", e.GetType().ToString() },
                        { "Message", e.Message },
                        { "Channel ID", i.Snippet.ResourceId.ChannelId },
                        { "StackTrace", e.StackTrace }
                    });
                        continue;
                    }
                ApplicationData.Current.RoamingSettings.Values["subscriptions"] = JsonConvert.SerializeObject(subs);
            }
            catch (Exception e)
            {
                Analytics.TrackEvent("Failed to write user's subscriptions", new Dictionary<string, string>
                {
                    { "Exception", e.GetType().ToString() },
                    { "Message", e.Message },
                    { "StackTrace", e.StackTrace }
                });
            }
        }

        /// <summary>
        /// Deauthenticates current user
        /// </summary>
        public static async void Deauthenticate()
        {
            if (!await Credential.RevokeTokenAsync(CancellationToken.None))
                return;
            
            Credential = null;
            UserChannel = null;
            UserInfo = null;
            History.Clear();
            WatchLater.Clear();
            Subscriptions.Clear();
            ApplicationData.Current.RoamingSettings.Values["subscriptions"] = "";

            AuthorizationStateChanged?.Invoke(args: false);
            SettingsStorage.HasAccount = false;
        }

        /// <summary>
        /// Checks if any user has already been logged in. If has, calls *Authorize()* to retrieve his info
        /// </summary>
        /// <param name="retrieveSubs">Loads user's subscriptions if true</param>
        public static void CheckAuthorization(bool retrieveSubs = true)
        {
            if (SettingsStorage.HasAccount)
                Authorize(retrieveSubs);
            else
            {
                AuthorizationStateChanged.Invoke(args: false);
                ApplicationData.Current.RoamingSettings.Values["subscriptions"] = "";
            }
        }

        /// <summary>
        /// Connects to MS Store and checks if user has bought ad-free
        /// </summary>
        public static async void CheckAddons()
        {
            try
            {
                StoreProductQueryResult requset = await StoreContext.GetDefault().GetAssociatedStoreProductsAsync(new[] { "Durable" });

                if (requset.Products["9NP1QK556625"].IsInUserCollection)
                    return;

                AdsDisabled = false;
                Purchased?.Invoke(null, false, requset.Products["9NP1QK556625"].Price.FormattedPrice);
            }
            catch { }
        }

        public static async void GetAdblock()
        {
            StorePurchaseResult request = await StoreContext.GetDefault().RequestPurchaseAsync("9NP1QK556625");

            switch (request.Status)
            {
                case StorePurchaseStatus.AlreadyPurchased:
                case StorePurchaseStatus.Succeeded:
                    Purchased?.Invoke(args: true);
                    AdsDisabled = true;
                    break;
            }
        }
        #endregion
    }
}
