using Azure.Identity;
using Microsoft.Graph;
using Microsoft.Graph.Models;

namespace Fast.Logic;

public static partial class Util
{
    public class SharePoint
    {
        private static readonly string siteName = "superiornatgas.sharepoint.com:/sites/T-Rex";

        public SharePoint()
        {

        }

        private static ClientSecretCredential GetGraphCredential()
        {
            var db = ProgramShared.CreateContext();
            var settings = db.AppSettings.Where(s => s.Name == "Graph-TenantID" || s.Name == "Graph-ClientID" || s.Name == "Graph-ClientSecret").ToDictionary(s => s.Name, s => s.Value);
            var tenantId = settings.GetValueOrDefault("Graph-TenantID") ?? throw new InvalidOperationException("Missing Graph-TenantID in app_setting");
            var clientId = settings.GetValueOrDefault("Graph-ClientID") ?? throw new InvalidOperationException("Missing Graph-ClientID in app_setting");
            var clientSecret = settings.GetValueOrDefault("Graph-ClientSecret") ?? throw new InvalidOperationException("Missing Graph-ClientSecret in app_setting");
            return new ClientSecretCredential(tenantId, clientId, clientSecret);
        }

        private static string GetEnvironmentRootFolder()
        {
            if (Main.IsTestDb)
                return "Test";
            if (Main.IsDevEnvOrDb)
                return "Dev";
            return string.Empty;
        }

        public static string CombineEnvironmentPath(string? relativePath)
        {
            var envFolder = GetEnvironmentRootFolder();
            if (string.IsNullOrEmpty(envFolder))
                return relativePath ?? string.Empty;
            if (string.IsNullOrEmpty(relativePath))
                return envFolder;
            return Path.Combine(envFolder, relativePath).Replace("\\", "/").TrimStart('/');
        }

        public async Task<List<DriveItem>> GetNestedFilesForFolderAsync(string folderId)
        {
            var results = new List<DriveItem>();
            var driveId = await DriveId.Value;
            var folderItems = await GraphClient.Value.Drives[driveId].Items[folderId].Children.GetAsync();
            if (folderItems?.Value == null)
                return results;
            foreach (var item in folderItems.Value)
            {
                if (item.Folder != null)
                {
                    var subResults = await GetNestedFilesForFolderAsync(item.Id!);
                    results.AddRange(subResults);
                }
                else if (item.File != null)
                {
                    results.Add(item);
                }
            }
            return results;
        }

        private async Task<DriveItem?> GetFileByNameAsync(string driveFolder, string fileNameOnDisk)
        {
            var driveId = await DriveId.Value;
            var shardFolderName = File.CreateShardedSubdirectory("", fileNameOnDisk, false);
            if (string.IsNullOrEmpty(shardFolderName))
                return null;

            var pathPieces = new List<string>();
            if (!string.IsNullOrWhiteSpace(driveFolder))
                pathPieces.Add(driveFolder);

            pathPieces.Add(shardFolderName);
            pathPieces.Add(fileNameOnDisk);

            var filePath = string.Join("/", pathPieces);
            var fileItem = await GraphClient.Value.Drives[driveId].Items["root"].ItemWithPath(filePath).GetAsync();
            if (fileItem == null)
                return null;

            return fileItem;
        }

        public async Task<string?> CreateFolder(string driveId, string parentFolderId, string folderName, DriveItemCollectionResponse? parentFolderChildren = null)
        {
            try
            {
                parentFolderChildren ??= await GraphClient.Value.Drives[driveId].Items[parentFolderId].Children.GetAsync();
                var existingFolderMatch = parentFolderChildren?.Value?.FirstOrDefault(i => string.Equals(i?.Name, folderName, StringComparison.OrdinalIgnoreCase) && i?.Folder != null);
                if (existingFolderMatch != null)
                    return existingFolderMatch.Id;

                var newFolder = new DriveItem
                {
                    Name = folderName,
                    Folder = new Folder(),
                    AdditionalData = new Dictionary<string, object> { { "@microsoft.graph.conflictBehavior", "fail" } }
                };

                var createdFolder = await GraphClient.Value.Drives[driveId].Items[parentFolderId].Children.PostAsync(newFolder);
                return createdFolder?.Id;
            }
            catch (Exception)
            {
                return null;
            }
        }

        public async Task<string?> CreateFoldersByPath(string driveId, string? path)
        {
            if (string.IsNullOrWhiteSpace(path))
                return "root";

            var folderNames = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
            string parentFolderId = "root";

            var parentFolderChildren = await GraphClient.Value.Drives[driveId].Items[parentFolderId].Children.GetAsync();
            foreach (var folderName in folderNames)
            {
                var nextFolderId = await CreateFolder(driveId, parentFolderId, folderName, parentFolderChildren);
                if (nextFolderId == null)
                    return null;
                parentFolderId = nextFolderId;
            }

            return parentFolderId;
        }

        public async Task<Stream?> DownloadFileAsync(string driveFolder, string fileNameOnDisk)
        {
            var adjustedDriveFolder = CombineEnvironmentPath(driveFolder);
            var driveItem = await GetFileByNameAsync(adjustedDriveFolder, fileNameOnDisk);
            if (driveItem?.Id == null)
                return null;

            var driveId = await DriveId.Value;
            var fileStream = await GraphClient.Value.Drives[driveId].Items[driveItem.Id].Content.GetAsync();
            return fileStream;
        }

        public async Task<bool> DeleteFileAsync(string driveFolder, string fileNameOnDisk)
        {
            var adjustedDriveFolder = CombineEnvironmentPath(driveFolder);
            var driveItem = await GetFileByNameAsync(adjustedDriveFolder, fileNameOnDisk);
            if (driveItem == null)
                return false;

            var driveId = await DriveId.Value;
            await GraphClient.Value.Drives[driveId].Items[driveItem.Id].DeleteAsync();
            return true;
        }

        public async Task<string?> UploadFileAsync(string fileNameOnDisk, string driveFolderPath, IFormFile file, bool shardFile = true)
        {
            var driveId = await DriveId.Value;
            driveFolderPath = driveFolderPath.Replace("\\", "/").TrimStart('/');
            var adjustedFolderPath = CombineEnvironmentPath(driveFolderPath);
            var driveFolderId = await CreateFoldersByPath(driveId, adjustedFolderPath);
            if (driveFolderId == null)
                return null;

            string parentFolderId = driveFolderId;

            if (shardFile)
            {
                var shardFolderName = File.CreateShardedSubdirectory("", fileNameOnDisk, false);
                if (string.IsNullOrEmpty(shardFolderName))
                    return null;

                var shardFolderId = await CreateFolder(driveId, driveFolderId, shardFolderName);
                if (shardFolderId == null)
                    return null;

                parentFolderId = shardFolderId;
            }

            var body = new Microsoft.Graph.Drives.Item.Items.Item.CreateUploadSession.CreateUploadSessionPostRequestBody
            {
                Item = new DriveItemUploadableProperties
                {
                    AdditionalData = new Dictionary<string, object> { { "@microsoft.graph.conflictBehavior", "replace" } }
                }
            };

            await using var fileStream = new MemoryStream();
            await file.CopyToAsync(fileStream);
            fileStream.Position = 0;

            var uploadItem = await GraphClient.Value.Drives[driveId].Items[parentFolderId].ItemWithPath(fileNameOnDisk).Content.PutAsync(fileStream);
            if (uploadItem?.Id != null)
            {
                var isCheckedOut = await IsCheckedOut(uploadItem.Id);
                if (isCheckedOut)
                    await GraphClient.Value.Drives[driveId].Items[uploadItem.Id!].Checkin.PostAsync(new());
                return fileNameOnDisk;
            }

            return null;
        }

        public static readonly Lazy<GraphServiceClient> GraphClient = new(() =>
        {
            var graphCredential = GetGraphCredential();
            var graphClient = new GraphServiceClient(graphCredential, new[] { "https://graph.microsoft.com/.default" });
            return graphClient;
        });

        public static readonly Lazy<Task<string>> DriveId = new(async () =>
        {
            var site = await GraphClient.Value.Sites[siteName].GetAsync();
            var siteId = site?.Id;

            var siteDrives = await GraphClient.Value.Sites[siteId].Drives.GetAsync();
            var drive = siteDrives?.Value?.FirstOrDefault(d => string.Equals(d?.Name, "documents", StringComparison.OrdinalIgnoreCase));
            return drive?.Id ?? "";
        });

        private static async Task<bool> IsCheckedOut(string itemId)
        {
            var driveId = await DriveId.Value;
            var item = await GraphClient.Value.Drives[driveId].Items[itemId].GetAsync(rc =>
            {
                rc.QueryParameters.Select = new[] { "id", "name", "publication" };
            });

            var isCheckedOut = string.Equals(item?.Publication?.Level, "checkout", StringComparison.OrdinalIgnoreCase);
            return isCheckedOut;
        }
    }
}
