using System;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Services;
using Google.Apis.Drive.v3;
using Google.Apis.Util.Store;
using Google.Apis.Drive.v3.Data;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Services;

namespace AsbCloudInfrastructure.Services
{
    public class GoogleDriveService : IFileShareService, IDisposable
    {
        private readonly DriveService service;

        const string applicationName = "FileSharing";
        const string username = "asbautodrilling@gmail.com";
        const string redirectUri = "http://autodrilling.naftagaz.com/AuthCallback/IndexAsync";
        const string clientId = "1020584579240-f7amqg35qg7j94ta1ntgitajq27cgh49.apps.googleusercontent.com";
        const string clientSecret = "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";
        const string authorizationCode = "4/0AX4XfWjAV_cwaXdtBHHIseToLqI36PeMycW7wMCdZBmumADKu0Ov2AoVg3F6NDRCApmW8A";
        const string refreshToken = "1//04rHpwsBJqXWyCgYIARAAGAQSNwF-L9IrzEaVHpC_Ajim5ZF0_hlSyOOW-QFARlRx4Xanx_H9TxyuRJSCIMmdVDf6S-qejlGli54";
        //const string accessToken = "ya29.a0ARrdaM8jLcdNDylpV70X08ix-pqU-1QfLbmQy4iRb7KWUgl3keukmd2mx5AxraEO0eveR3I_p1EacrgtlbbxtNWbXxl_YPf4mQTbhhNaoltp2aSn6VndUlyydLDKzw9J9r8ouFNnVZip9fivBmc-AX_rWXsj";

        static readonly IAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
            {
                ClientSecrets = new ClientSecrets
                {
                    ClientId = clientId,
                    ClientSecret = clientSecret
                },
                
                Scopes = new[] { DriveService.Scope.Drive },
                DataStore = new FileDataStore(applicationName),//TODO: replace FileDataStore by thread safe static datastore service
        });

        public GoogleDriveService()
        {
            service = MakeDriveServiceAsync(CancellationToken.None).Result;
        }

        ~GoogleDriveService()
        {
            Dispose();
        }

        private async Task<DriveService> MakeDriveServiceAsync(CancellationToken cancellationToken)
        {
            var token = await flow.LoadTokenAsync(username, cancellationToken).ConfigureAwait(false);

            if (flow.ShouldForceTokenRetrieval() || token is null || token.IsExpired(flow.Clock))
            {
                token = await flow.RefreshTokenAsync(clientId, refreshToken, cancellationToken).ConfigureAwait(false);
                //token = await flow.ExchangeCodeForTokenAsync(clientId, authorizationCode, redirectUri, cancellationToken).ConfigureAwait(false);
                await flow.DataStore.StoreAsync(username, token).ConfigureAwait(false);
            }
            
            var credential = new UserCredential(flow, username, token);

            var newService = new DriveService(new BaseClientService.Initializer
            {
                HttpClientInitializer = credential,
                ApplicationName = applicationName
            });
            return newService;
        }

        public async Task<IEnumerable<string>> GetAllFileNames()
        {
            var fileList = service.Files.List();
            fileList.Fields = "files(id, webViewLink, size)";
        
            var result = new List<Google.Apis.Drive.v3.Data.File>();
            string pageToken = null;
            do
            {
                fileList.PageToken = pageToken;
                var filesResult = await fileList.ExecuteAsync();
                var files = filesResult.Files;
                pageToken = filesResult.NextPageToken;
                result.AddRange(files);
            } while (pageToken != null);
        
            return result.Select(r => r.Name);
        }

        public async Task<string> GetFileWebLinkAsync(string idFile,
            CancellationToken token = default)
        {
            var fileList = service.Files.List();
            fileList.Fields = "files(id, webViewLink, size)";
            var filesResult = await fileList.ExecuteAsync(token)
                .ConfigureAwait(false);
            var file = filesResult.Files.FirstOrDefault(f => f.Id == idFile);
            return file?.WebViewLink ?? string.Empty;
        }
        
        public async Task<string> CreateFolderAsync(string folderName,
            CancellationToken token = default) 
        {                                       
            var driveFolder = new Google.Apis.Drive.v3.Data.File();
            driveFolder.Name = folderName;
            driveFolder.MimeType = "application/vnd.google-apps.folder";
            //driveFolder.Parents = new string[] { parent };
            var command = service.Files.Create(driveFolder);
            var file = await command.ExecuteAsync(token)
                .ConfigureAwait(false);
            return file.Id;
        }

        public async Task CreatePublicPermissionForFileAsync(string idFile,
            CancellationToken token = default)
        {
            var permission = new Permission() { Type = "anyone", Role = "reader"};
            var addPermissionRequest = service.Permissions.Create(permission, idFile);
            await addPermissionRequest.ExecuteAsync(token)
                .ConfigureAwait(false);
        }
        
        public async Task<string> UploadFileAsync(Stream file, string fileName, string fileMime, 
            string fileDescription, CancellationToken token = default)
        {
            var driveFile = new Google.Apis.Drive.v3.Data.File();
            driveFile.Name = fileName;
            driveFile.Description = fileDescription;
            driveFile.MimeType = fileMime;
            //driveFile.Parents = new [] {folder};
    
            var request = service.Files.Create(driveFile, file, fileMime);
            request.Fields = "id";
    
            var response = await request.UploadAsync(token)
                .ConfigureAwait(false);
            if (response.Status != Google.Apis.Upload.UploadStatus.Completed)
                throw response.Exception;
    
            return request.ResponseBody.Id;
        }

        public async Task DeleteFileAsync(string fileId,
            CancellationToken token = default)
        {
            var command = service.Files.Delete(fileId);
            await command.ExecuteAsync(token)
                .ConfigureAwait(false);
        }

#pragma warning disable CA1816 // Методы Dispose должны вызывать SuppressFinalize
        public void Dispose()
        {
            service?.Dispose();
        }
#pragma warning restore CA1816 // Методы Dispose должны вызывать SuppressFinalize

        public async Task<string> PublishFileToCloudAsync(string filePath, string originalName, CancellationToken token)
        {
            await using var fileStream = System.IO.File.Open(filePath, FileMode.Open);
            var uploadedFileId = await UploadFileAsync(fileStream, originalName,
                    "", "uploaded", token)
                .ConfigureAwait(false);

            await CreatePublicPermissionForFileAsync(uploadedFileId, token)
                .ConfigureAwait(false);

            var webLink = await GetFileWebLinkAsync(uploadedFileId, token)
                .ConfigureAwait(false);

            return webLink;
        }
    }
}