using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;

using Newtonsoft.Json.Linq;

using C4IT.Logging;
using System.Reflection;
using static C4IT.Logging.cLogManager;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using C4IT.HTTP;

namespace C4IT.MsGraph
{
    public class cMsGraphSharepoint
    {
        public const string constMsGraphTeamsList = "groups/?$filter=resourceProvisioningOptions/Any(x:x eq 'Team')&$select=id,displayName";
        public const string constMsGraphGroupInfo = "groups/{id}";
        public const string constMsGraphGroupMembers = "groups/{id}/members";
        public const string constMsGraphGroupMembersAdd = "groups/{id}/members/$ref";
        public const string constMsGraphGroupMembersDelete = "groups/{id}/members/{memberId}/$ref";
        public const string constMsGraphGroupOwnersAdd = "groups/{id}/owners/$ref";
        public const string constMsGraphGroupOwnersDelete = "groups/{id}/owners/{memberId}/$ref";
        public const string constMsGraphGroupTransitiveMembers = "groups/{id}/transitiveMembers";
        public const string constMsGraphGroupOwners = "groups/{id}/owners";
        public const string constMsGraphGroupDrive = "groups/{id}/drive";
        public const string constMsGraphDriveInfo = "drives/{id}";
        public const string constMsGraphDriveRoot = "drives/{id}/root";
        public const string constMsGraphFileObjectInfo = "drives/{driveId}/items/{id}";
        public const string constMsGraphFileObjectChildren = "drives/{driveId}/items/{itemId}/children/?$select=id,name,file";
        public const string constMsGraphFileObjectByName = "drives/{driveId}/items/{itemId}/children/?$select=id,name,file&$filter=name eq '{name}'";
        public const string constMsGraphFileObjectPermissions = "/drives/{driveId}/items/{id}/permissions";
        public const string constMsGraphTeamChannelList = "teams/{id}/channels";
        public const string constMsGraphTeamClone = "teams/{id}/clone";
        public const string constMsGraphTeamPrivateChannelList = "teams/{id}/channels?$filter=membershipType eq 'private'";
        public const string constMsGraphTeamChannelInfo = "teams/{groupId}/channels/{id}";
        public const string constMsGraphTeamChannelRoot = "teams/{groupId}/channels/{id}/filesFolder";
        public const string constMsGraphTeamFileObjectPermissionInvite = "drives/{driveId}/items/{id}/invite";
        public const string constMsGraphTeamFileObjectPermissionDelete = "drives/{driveId}/items/{itemId}/permissions/{id}";
        public const string constMsGraphTeamFileObjectPermissionRevokeGrant = "drives/{driveId}/items/{itemId}/permissions/{id}/revokeGrants";
        public const string constMsGraphUserInfo = "users/{email}";
        public cMsGraphBase Base { get; private set; } = null;

        public bool IsOnline
        {
            get
            {
                if (Base == null)
                    return false;
                return Base.IsOnline;
            }
        }

        public cMsGraphSharepoint(cMsGraphBase Base)
        {
            this.Base = Base;
        }

        public async Task<cMsGraphCollectionGroups> RequestTeamsListAsync()
        {
            Base.ResetError();
            try
            {
                var Result = await Base.RequestListAsync(constMsGraphTeamsList, UseBeta: true, retryForbidden: true);

                if (Result != null)
                {
                    var RetVal = new cMsGraphCollectionGroups(Result.Count);
                    foreach (var Entry in Result)
                    {
                        var res = new cMsGraphResultGroup(Entry);
                        RetVal.Add(res);
                    }
                    return RetVal;
                }
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }

            return null;
        }

        public async Task<cMsGraphResultGroup> RequestGroupInfoAsync(string ID)
        {
            Base.ResetError();
            try
            {
                var strRequest = constMsGraphGroupInfo.Replace("{id}", ID);
                var Result = await Base.RequestAsync(strRequest);
                if (Result != null)
                    return new cMsGraphResultGroup(Result);
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }

            return null;
        }

        public async Task<cMsGraphResultBase> CloneTeam(string ID, CloneTeamRequest cloneTeamRequest)
        {
            Base.ResetError();
            try
            {
                var strRequest = constMsGraphTeamClone.Replace("{id}", ID);
                var Result = await Base.RequestAsync(strRequest, false, eHttpMethod.post, cloneTeamRequest);
                var Result2 = await GetOperationResult(Result.Result);
                    //Base.RequestAsync(Result.Result, false, cMsGraphBase.eHttpMethod.get);
                if (Result2 != null)
                    return Result2;
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }

            return null;
        }
        public async Task<cMsGraphResultBase> GetOperationResult(string operationUrl)
        {
            Base.ResetError();

            const int maxAttempts = 5;
            const int delaySeconds = 30;

            cMsGraphResultBase lastResult = null;

            for (int attempt = 1; attempt <= maxAttempts; attempt++)
            {
                try
                {
                    var result = await Base.RequestAsync(operationUrl, false, eHttpMethod.get);
                    lastResult = result; // für den Fall, dass wir nach maxAttempts etwas zurückgeben wollen

                    // Wenn gar nichts kam -> als transient behandeln und ggf. erneut versuchen
                    if (result?.Result == null)
                    {
                        if (attempt == maxAttempts)
                        {
                            cLogManager.LogEntry(
                                $"Maximale Anzahl Versuche erreicht ({maxAttempts}). Keine gültige Antwort erhalten.",
                                LogLevels.Error);
                            return lastResult;
                        }

                        await Task.Delay(TimeSpan.FromSeconds(delaySeconds));
                        continue;
                    }

                    var status = result.Result.status;

                    if (status == null)
                    {
                        if (attempt == maxAttempts)
                        {
                            cLogManager.LogEntry(
                                $"Maximale Anzahl Versuche erreicht ({maxAttempts}). Statusfeld war null.",
                                LogLevels.Error);
                            return result;
                        }

                        await Task.Delay(TimeSpan.FromSeconds(delaySeconds));
                        continue;
                    }

                    if (status.Equals("notStarted", StringComparison.OrdinalIgnoreCase) ||
                        status.Equals("inProgress", StringComparison.OrdinalIgnoreCase))
                    {
                        if (attempt == maxAttempts)
                        {
                            cLogManager.LogEntry(
                                $"Maximale Anzahl Versuche erreicht ({maxAttempts}). Operation noch nicht abgeschlossen.",
                                LogLevels.Error);
                            return result;
                        }

                        await Task.Delay(TimeSpan.FromSeconds(delaySeconds));
                        continue;
                    }

                    if (status.Equals("failed", StringComparison.OrdinalIgnoreCase))
                    {
                        cLogManager.LogEntry($"Operation failed: {result.Result.error?.ToString() ?? "Unknown error"}",
                            LogLevels.Error);
                        return result;
                    }

                    if (status.Equals("succeeded", StringComparison.OrdinalIgnoreCase))
                    {
                        return result;
                    }

                    // Unerwarteter Status
                    cLogManager.LogEntry($"Unexpected operation status: {status}", LogLevels.Warning);
                    return result;
                }
                catch (Exception ex)
                {
                    cLogManager.LogException(ex);

                    // Bei letzter Iteration abbrechen, sonst noch einmal versuchen
                    if (attempt == maxAttempts)
                        return lastResult;

                    await Task.Delay(TimeSpan.FromSeconds(delaySeconds));
                }
            }

            cLogManager.LogEntry($"Operation nicht abgeschlossen nach {maxAttempts} Versuchen.", LogLevels.Error);
            return lastResult;
        }


        public async Task<cMsGraphResultUser> RequestUserInfoAsync(string email)
        {
            Base.ResetError();
            try
            {
                var strRequest = constMsGraphUserInfo.Replace("{email}", email);
                var Result = await Base.RequestAsync(strRequest);
                if (Result != null)
                    return new cMsGraphResultUser(Result);
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }

            return null;
        }

        public async Task<cMsGraphCollectionUsers> RequestGroupMembersAsync(string GroupID, string UserId = null)
        {
            return await privRequestGroupMembersAsync(constMsGraphGroupMembers, GroupID, UserId);
        }

        public async Task<cMsGraphCollectionUsers> RequestGroupTransitiveMembers(string GroupID, string UserId = null)
        {
            return await privRequestGroupMembersAsync(constMsGraphGroupTransitiveMembers, GroupID, UserId);
        }

        public async Task<cMsGraphCollectionUsers> RequestGroupOwnersAsync(string GroupID, string UserId = null)
        {
            return await privRequestGroupMembersAsync(constMsGraphGroupOwners, GroupID, UserId);
        }

        private async Task<cMsGraphCollectionUsers> privRequestGroupMembersAsync(string Url, string GroupID, string UserId)
        {
            Base.ResetError();
            try
            {
                var Url2 = Url.Replace("{id}", GroupID);
                if (!string.IsNullOrEmpty(UserId))
                    Url2 += $"/?$filter=id eq '{UserId}'";
                var Result = await Base.RequestListAsync(Url2);

                if (Result != null)
                {
                    var RetVal = new cMsGraphCollectionUsers(Result.Count);
                    foreach (var Entry in Result)
                    {
                        var res = new cMsGraphResultUser(Entry);
                        if (!string.IsNullOrEmpty(res.UserPrincipalName))
                            RetVal.Add(res);
                    }
                    return RetVal;
                }
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }

            return null;
        }

        public async Task<cMsGraphResultDrive> RequestGroupDrive(string GroupID)
        {
            Base.ResetError();
            try
            {
                var strRequest = constMsGraphGroupDrive.Replace("{id}", GroupID);
                var Result = await Base.RequestAsync(strRequest);
                if (Result != null)
                    return new cMsGraphResultDrive(Result);
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }

            return null;
        }

        public async Task<cMsGraphResultDrive> RequestDriveInfo(string DriveID)
        {
            Base.ResetError();
            try
            {
                var strRequest = constMsGraphDriveInfo.Replace("{id}", DriveID);
                var Result = await Base.RequestAsync(strRequest);
                if (Result != null)
                {
                    var RetVal = new cMsGraphResultDrive(Result);
                    return RetVal;
                }
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }

            return null;
        }

        public async Task<cMsGraphResultFileObject> RequestDriveRoot(cMsGraphResultDrive Drive)
        {
            Base.ResetError();
            try
            {
                var strRequest = constMsGraphDriveRoot.Replace("{id}",Drive.ID);
                var Result = await Base.RequestAsync(strRequest);
                if (Result != null)
                    return new cMsGraphResultFileObject(Drive.ID, Result, Drive);
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }

            return null;
        }

        public async Task<cMsGraphResultFileObject> RequestFileObjectInfo(string DriveID, string ID, cMsGraphResultDrive Drive = null)
        {
            Base.ResetError();
            try
            {
                var strRequest = constMsGraphFileObjectInfo.Replace("{driveId}", DriveID).Replace("{id}", ID);
                var Result = await Base.RequestAsync(strRequest);
                if (Result != null)
                {
                    var RetVal = new cMsGraphResultFileObject(DriveID, Result, Drive);
                    return RetVal;
                }
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }

            return null;
        }

        public async Task<cMsGraphResultFileObject> RequestFileObjectByName(cMsGraphResultFileObject Folder, string Name)
        {
            Base.ResetError();
            if (Folder == null || Folder.DriveID == null)
                return null;


            try
            {
                var strName = HttpUtility.UrlEncode(Name);
                var strRequest = constMsGraphFileObjectByName.Replace("{driveId}", Folder.DriveID);
                strRequest = strRequest.Replace("{itemId}", Folder.ID);
                strRequest = strRequest.Replace("{name}", strName);

                var Result = await Base.RequestAsync(strRequest);
                if (Result != null)
                {
                    if (Result.Result.value.Count >= 1)
                    {
                        dynamic d = Result.Result.value[0];
                        var f = new cMsGraphResultBase(d);
                        return new cMsGraphResultFileObject(Folder.DriveID, f, Folder.Drive);
                    }
                }
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }

            return null;

        }

        public async Task<cMsGraphCollectionFileObjects> RequestFileObjectSubitems(string DriveID, string ID, bool Folders, cMsGraphResultDrive Drive = null)
        {
            Base.ResetError();
            try
            {
                var strRequest = constMsGraphFileObjectChildren.Replace("{driveId}", DriveID).Replace("{itemId}", ID);
                var Result = await Base.RequestListAsync(strRequest);
                if (Result != null)
                {
                    var RetVal = new cMsGraphCollectionFileObjects();
                    foreach (var Entry in Result)
                    {
                        var item = new cMsGraphResultFileObject(DriveID, Entry, Drive);
                        if (item.IsFolder ^ !Folders)
                            RetVal.Add(item);
                    }
                    return RetVal;
                }
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }

            return null;
        }

        public async Task<cMsGraphCollectionPermissions> RequestFileObjectPermissions(cMsGraphResultFileObject Item)
        {
            Base.ResetError();
            try
            {
                if (Item.DriveID == null)
                    return null;

                var strRequest = constMsGraphFileObjectPermissions.Replace("{driveId}", Item.DriveID).Replace("{id}", Item.ID);
                var Result = await Base.RequestListAsync(strRequest);
                if (Result != null)
                {
                    var RetVal = new cMsGraphCollectionPermissions();
                    foreach (var Entry in Result)
                    {
                        var item = cMsGraphResultPermission.CreateInstance(Item, Entry);
                        if (item != null)
                            RetVal.Add(item);
                    }
                    return RetVal;
                }
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }

            return null;
        }

        public async Task<cMsGraphCollectionChannels> RequestTeamChannels(cMsGraphResultGroup Team, bool OnlyPrivate = false)
        {
            Base.ResetError();
            try
            {
                var strUrl = constMsGraphTeamChannelList;
                if (OnlyPrivate)
                    strUrl = constMsGraphTeamPrivateChannelList;
                var strRequest = strUrl.Replace("{id}", Team.ID);
                var Result = await Base.RequestListAsync(strRequest, retryForbidden: true);
                if (Result != null)
                {
                    var RetVal = new cMsGraphCollectionChannels(Result.Count);
                    foreach (var Entry in Result)
                    {
                        var item = new cMsGraphResultChannel(Team.ID, Entry, Team);
                        RetVal.Add(item);
                    }
                    return RetVal;
                }
                else
                {
                }
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }

            return null;
        }

        public async Task<cMsGraphResultChannel> RequestChannelInfo(string TeamsID, string ChannelID, cMsGraphResultGroup Team = null)
        {
            Base.ResetError();
            try
            {
                var strRequest = constMsGraphTeamChannelInfo.Replace("{groupId}", TeamsID).Replace("{id}", ChannelID);
                var Result = await Base.RequestAsync(strRequest);
                if (Result != null)
                {
                    var RetVal = new cMsGraphResultChannel(TeamsID, Result, Team);
                    return RetVal;
                }
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }

            return null;
        }

        public async Task<cMsGraphResultChannelRoot> RequestChannelRoot(cMsGraphResultChannel Channel)
        {
            Base.ResetError();
            try
            {
                var strRequest = constMsGraphTeamChannelRoot.Replace("{groupId}", Channel.TeamID).Replace("{id}", Channel.ID);
                var Result = await Base.RequestAsync(strRequest, retryForbidden: true);
                if (Result != null)
                {
                    var RetVal = new cMsGraphResultChannelRoot(Channel, Result);
                    return RetVal;
                }
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }

            return null;
        }

        public async Task<bool> AddGroupMembership(cMsGraphResultGroup group, bool owner, cMsGraphResultUser user)
        {
            Base.ResetError();
            try
            {
                string strRequest;
                if (owner)
                    strRequest = constMsGraphGroupOwnersAdd;
                else
                    strRequest = constMsGraphGroupMembersAdd;

                strRequest= strRequest.Replace("{id}", group.ID);
                var Request = new AddGroupMemberRequest()
                {
                    oDataId = user.ODataId
                };
                var Result = await Base.RequestAsync(strRequest, httpMethod: eHttpMethod.post, JsonData: Request);
                if (Result == null)
                    return false;

            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }

            return false;
        }

        public async Task<bool> DeleteGroupMembership(cMsGraphResultGroup group, bool owner, cMsGraphResultUser user)
        {
            Base.ResetError();
            try
            {
                string strRequest;
                if (owner)
                    strRequest = constMsGraphGroupOwnersDelete;
                else
                    strRequest = constMsGraphGroupMembersDelete;

                strRequest = strRequest.Replace("{id}", group.ID).Replace("{memberId}", user.ID);
                var Result = await Base.RequestAsync(strRequest, httpMethod: eHttpMethod.delete);
                if (Result == null)
                    return false;

            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }

            return false;
        }

        public async Task<List<cMsGraphResultPermission>> AddFileObjectPermission(cMsGraphResultFileObject Item, IEnumerable<cMsGraphResultPermission.ePermissionRole> Roles, cMsGraphResultPermission.eDriveReceipientType RecipientType, string RecipientID, bool RequireSignIn = true, bool SendInvitation = false, string Message = null)
        {
            Base.ResetError();
            try
            {
                if (Item?.ID == null)
                    return null;
                if (Item?.DriveID == null)
                    return null;

                var Recipient = DriveRecipient.Create(RecipientType, RecipientID);
                var Recipients = new List<DriveRecipient>() { Recipient };

                var roles = new List<string>();
                foreach (var R in Roles)
                    roles.Add(R.ToString());

                if (!SendInvitation)
                    RequireSignIn = true;

                var Request = new AddPermissionRequest()
                {
                    requireSignIn = RequireSignIn,
                    sendInvitation = SendInvitation,
                    message = Message,
                    recipients = Recipients,
                    roles = roles
                };

                var strRequest = constMsGraphTeamFileObjectPermissionInvite.Replace("{driveId}", Item.DriveID).Replace("{id}", Item.ID);

                var Result = await Base.RequestAsync(strRequest, httpMethod: eHttpMethod.post, JsonData: Request);
                if (Result == null)
                    return null;
                if (!Result.Result.TryGetValue("value", out JToken Value))
                    return null;

                if (Result.Result.value.Count <= 0)
                    return null;

                var RetVal = new List<cMsGraphResultPermission>();

                foreach (dynamic Entry in Result.Result.value)
                {
                    var Entry2 = new cMsGraphResultBase(Entry);
                    var Perm = cMsGraphResultPermission.CreateInstance(Item, Entry2);
                    RetVal.Add(Perm);
                }
                return RetVal;
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }

            return null;
        }

        public async Task<bool> DeleteFileObjectPermission(cMsGraphResultPermission Permission)
        {
            Base.ResetError();
            try
            {
                if (Permission?.ID == null)
                    return false;
                if (Permission?.Item?.ID == null)
                    return false;
                if (Permission?.Item?.DriveID == null)
                    return false;

                var strRequest = constMsGraphTeamFileObjectPermissionDelete.Replace("{driveId}", Permission.Item.DriveID).Replace("{itemId}", Permission.Item.ID).Replace("{id}", Permission.ID);

                var Result = await Base.RequestAsync(strRequest, httpMethod: eHttpMethod.delete);
                if (Result != null)
                    return true;
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }

            return false;
        }

        public async Task<cMsGraphResultPermission> RevokeFileObjectPermissionGrant(cMsGraphResultPermission Permission, cMsGraphResultPermission.eDriveReceipientType RecipientType, string RecipientID)
        {
            Base.ResetError();
            try
            {
                if (Permission?.ID == null)
                    return null;
                if (Permission?.Item?.ID == null)
                    return null;
                if (Permission?.Item?.DriveID == null)
                    return null;

                var strRequest = constMsGraphTeamFileObjectPermissionRevokeGrant.Replace("{driveId}", Permission.Item.DriveID).Replace("{itemId}", Permission.Item.ID).Replace("{id}", Permission.ID);

                var Recipient = DriveRecipient.Create(RecipientType, RecipientID);
                var Grantees = new List<DriveRecipient>()
                {
                    Recipient
                };
                var RequestData = new RevokeGrantRequest()
                {
                    grantees = Grantees
                };

                var Result = await Base.RequestAsync(strRequest, UseBeta: true, httpMethod: eHttpMethod.post, JsonData: RequestData);
                if (Result != null)
                {
                    var RetVal = new cMsGraphResultPermission(Permission.Item, Result);
                    return RetVal;
                }
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }

            return null;
        }

        public class CloneTeamRequest
        {
            [JsonConverter(typeof(StringEnumConverter))]
            public enum TeamVisibilityType
            {
                [JsonProperty("private")]
                Private = 0,
                [JsonProperty("public")]
                Public = 1,
                [JsonProperty("hiddenMembership")]
                HiddenMembership = 2
            }

            [Flags]
            public enum ClonableTeamParts
            {
                None = 0,
                [JsonProperty("apps")]
                Apps = 1,
                [JsonProperty("tabs")]
                Tabs = 2,
                [JsonProperty("settings")]
                Settings = 4,
                [JsonProperty("channels")]
                Channels = 8,
                [JsonProperty("members")]
                Members = 16
            }

            [JsonProperty("displayName", NullValueHandling = NullValueHandling.Ignore)]
            public string DisplayName { get; set; }

            [JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
            public string Description { get; set; }

            [JsonProperty("mailNickname", NullValueHandling = NullValueHandling.Ignore)]
            public string MailNickname { get; set; }

            [JsonProperty("partsToClone", NullValueHandling = NullValueHandling.Ignore)]
            public string PartsToClone { get; set; }

            [JsonProperty("visibility")]
            [JsonConverter(typeof(StringEnumConverter))]
            public TeamVisibilityType Visibility { get; set; }

            public void SetClonableParts(ClonableTeamParts parts)
            {
                var partList = new System.Collections.Generic.List<string>();
                if (parts.HasFlag(ClonableTeamParts.Apps)) partList.Add("apps");
                if (parts.HasFlag(ClonableTeamParts.Tabs)) partList.Add("tabs");
                if (parts.HasFlag(ClonableTeamParts.Settings)) partList.Add("settings");
                if (parts.HasFlag(ClonableTeamParts.Channels)) partList.Add("channels");
                if (parts.HasFlag(ClonableTeamParts.Members)) partList.Add("members");
                PartsToClone = string.Join(",", partList);
            }
        }

        private class AddPermissionRequest
        {
            public bool requireSignIn { get; set; }
            public bool sendInvitation { get; set; }
            public string message { get; set; }
            public IEnumerable<string> roles { get; set; }
            public IEnumerable<DriveRecipient> recipients { get; set; }
        }
        private class AddGroupMemberRequest
        {
            [JsonProperty("@odata.id")]
            public string oDataId { get; set; }
         }

        private class DriveRecipient
        {
            public string email { get; set; }
            public string alias { get; set; }
            public string objectId { get; set; }

            public static DriveRecipient Create(cMsGraphResultPermission.eDriveReceipientType RecipientType, string RecipientID)
            {
                var Recipient = new DriveRecipient();
                switch (RecipientType)
                {
                    case cMsGraphResultPermission.eDriveReceipientType.objectId:
                        Recipient.objectId = RecipientID;
                        break;
                    case cMsGraphResultPermission.eDriveReceipientType.alias:
                        Recipient.email = RecipientID;
                        break;
                    default:
                        Recipient.email = RecipientID;
                        break;
                }
                return Recipient;
            }
        }

        private class RevokeGrantRequest
        {
            public IEnumerable<DriveRecipient> grantees { get; set; }
        }
    }

    public class cMsGraphResultGroup : cMsGraphResultBase
    {
        public string Description { get; private set; } = null;
        public string SID { get; private set; } = null;
        public string Visibility { get; private set; } = null;
        public string MailEnabled { get; private set; } = null;
        public string MailAddress { get; private set; } = null;

        public cMsGraphResultDrive Drive { get; private set; } = null;

        public cMsGraphCollectionChannels Channels { get; private set; } = null;

        public cMsGraphResultGroup(cMsGraphResultBase Result) : base(Result)
        {
            try { Description = Result.Result.description; } catch { };
            try { SID = Result.Result.securityIdentifier; } catch { };
            try { Visibility = Result.Result.visibility; } catch { };
            try { MailEnabled = Result.Result.mailEnabled; } catch { };
            try { MailAddress = Result.Result.mail; } catch { };
        }

        public async Task<bool> AddMember()
        {
            await Task.Delay(0);
            return false;
        }

        public async Task<bool> ResolveDrive(cMsGraphSharepoint SP)
        {
            Drive = await SP.RequestGroupDrive(ID);

            return Drive != null;
        }

        public async Task<bool> ResolveChannels(cMsGraphSharepoint SP, bool OnlyPrivate = false)
        {
            Channels = await SP.RequestTeamChannels(this, OnlyPrivate);

            return Channels != null;
        }

        public async Task<cMsGraphCollectionUsers> GetMembersAsync(cMsGraphSharepoint SP)
        {   
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                var RetVal = await SP.RequestGroupMembersAsync(this.ID);
                return RetVal;
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
    

        }

        public async Task<cMsGraphCollectionUsers> GetOwnersAsync(cMsGraphSharepoint SP)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                var RetVal = await SP.RequestGroupOwnersAsync(this.ID);
                return RetVal;
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }


        }
    }

    public class cMsGraphCollectionGroups : SortedList<string,string>
    {
        public cMsGraphCollectionGroups() {}
        public cMsGraphCollectionGroups(int n) : base(n) { }

        public void Add(cMsGraphResultGroup Group)
        {
            if (!string.IsNullOrEmpty(Group.DisplayName))
                if (!this.ContainsKey(Group.DisplayName))
                    this.Add(Group.DisplayName, Group.ID);
        }
    }

    public class cMsGraphResultChannel : cMsGraphResultBase
    {
        public string Description { get; private set; } = null;
        public string IsFavoriteByDefault { get; private set; } = null;
        public string MembershipType { get; private set; } = null;
        public string MailAddress { get; private set; } = null;

        public string TeamID { get; private set; } = null;
        public cMsGraphResultGroup Team { get; private set; } = null;

        public cMsGraphResultFileObject RootFolder { get; private set; } = null;
        public bool RootFolderResolved { get; private set; } = false;

        public cMsGraphResultChannel(string TeamID, cMsGraphResultBase Result, cMsGraphResultGroup Team = null) : base(Result)
        {
            this.TeamID = TeamID;
            this.Team = Team;
            try { Description = Result.Result.description; } catch { };
            try { IsFavoriteByDefault = Result.Result.isFavoriteByDefault; } catch { };
            try { MembershipType = Result.Result.membershipType; } catch { };
            try { MailAddress = Result.Result.mail; } catch { };
        }

        public async Task<bool> ResolveRootFolder(cMsGraphSharepoint SP)
        {
            if (Team == null)
            {
                Team = await SP.RequestGroupInfoAsync(TeamID);
                if (Team == null)
                    return false;
            }

            RootFolderResolved = true;

            if (Team.Drive == null)
                if (!await Team.ResolveDrive(SP))
                    return false;

            if (Team.Drive.Root == null)
                if (!await Team.Drive.ResolveRoot(SP))
                    return false;

            var RF = await SP.RequestChannelRoot(this);
            if (RF != null)
                if (!string.IsNullOrEmpty(RF.DriveID))
                {
                    cMsGraphResultDrive dr = null;
                    if ((Team.Drive != null) && (Team.Drive.ID == RF.DriveID))
                        dr = Team.Drive;
                    else if (await RF.ResolveDrive(SP))
                        dr = RF.Drive;

                    if (dr != null)
                        RootFolder = await SP.RequestFileObjectInfo(dr.ID, RF.ID, dr);
                }

            return RootFolder != null;
        }

    }

    public class cMsGraphCollectionChannels : SortedList<string, cMsGraphResultChannel>
    {
        public cMsGraphCollectionChannels() { }
        public cMsGraphCollectionChannels(int n) : base(n) { }

        public void Add(cMsGraphResultChannel Channel)
        {
            if (!this.ContainsKey(Channel.DisplayName))
                this.Add(Channel.DisplayName, Channel);
        }
    }

    public class cMsGraphResultDrive : cMsGraphResultBase
    {
        public string DriveType { get; private set; } = null;

        public cMsGraphResultFileObject Root { get; private set; } = null;

        public cMsGraphResultDrive(cMsGraphResultBase Result) : base(Result)
        {
            try { DriveType = Result.Result.driveType; } catch { };
        }

        public async Task<bool> ResolveRoot(cMsGraphSharepoint SP)
        {
            Root = await SP.RequestDriveRoot(this);

            return Root != null;
        }

    }

    public class cMsGraphResultPermission : cMsGraphResultBase
    {
        public enum eDriveReceipientType { email, alias, objectId};

        public enum ePermissionRole { owner, read, write };


        static public ePermissionRole getRole(string s)
        {
            switch (s.ToLowerInvariant())
            {
                case "owner":
                    return ePermissionRole.owner;
                case "write":
                    return ePermissionRole.write;
                default:
                    return ePermissionRole.read;
            }
        }

        public cMsGraphResultFileObject Item { get; private set; } = null;

        public List<cMsGraphPermissionIdentity> Identities { get; private set; } = new List<cMsGraphPermissionIdentity>();

        public List<ePermissionRole> Roles { get; private set; } = new List<ePermissionRole>();

        public bool isInherited { get; private set; } = false;

        public cMsGraphResultPermission(cMsGraphResultFileObject Item, cMsGraphResultBase Result) : base(Result)
        {
            try
            {
                this.Item = Item;

                if (Result.Result.TryGetValue("roles", out JToken JT1))
                {
                    Roles = new List<ePermissionRole>(Result.Result.roles.Count);
                    foreach (dynamic Entry in Result.Result.roles)
                        Roles.Add(getRole( Entry.ToString()));
                }

                if (Result.Result.TryGetValue("inheritedFrom", out JToken JT))
                {
                    isInherited = true;
                }
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }
        }

        static public cMsGraphResultPermission CreateInstance(cMsGraphResultFileObject Item, cMsGraphResultBase Result)
        {
            if (PermissionDirect.Check(Result.Result))
                return new PermissionDirect(Item, Result);

            if (PermissionLink.Check(Result.Result))
                return new PermissionLink(Item, Result);

            return new cMsGraphResultPermission(Item, Result);
        }

        public async Task<bool> Delete(cMsGraphSharepoint SP)
        {
            try
            {
                var RetVal = await SP.DeleteFileObjectPermission(this);
                return RetVal;
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }
            return false;
        }

        public async Task<bool> RevokeGrant(cMsGraphSharepoint SP, cMsGraphResultPermission.eDriveReceipientType RecipientType, string RecipientID)
        {
            try
            {
                if (this is cMsGraphResultPermission.PermissionDirect)
                {
                    var RetVal = await SP.DeleteFileObjectPermission(this);
                    return RetVal;
                }
                else if (this is cMsGraphResultPermission.PermissionLink)
                {
                    var RetVal = await SP.RevokeFileObjectPermissionGrant(this, RecipientType, RecipientID);
                    if (RetVal != null)
                        return true;
                }

            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }
            return false;
        }

        public class PermissionDirect : cMsGraphResultPermission
        {
            public PermissionDirect(cMsGraphResultFileObject Item, cMsGraphResultBase Result) :
                base(Item, Result)
            {
                try
                {
                    if (Result.Result.TryGetValue("grantedTo", out JToken JT1))
                    {
                        var Identity = new cMsGraphPermissionIdentity(Result.Result.grantedTo);
                        Identities.Clear();
                        Identities.Add(Identity);
                    }
                }
                catch (Exception E)
                {
                    cLogManager.LogException(E);
                }

            }

            public static bool Check(dynamic Result)
            {
                try
                {
                    if (Result.TryGetValue("grantedTo", out JToken JT1))
                        if (Result.grantedTo.TryGetValue("user", out JToken JT2))
                            return true;
                }
                catch (Exception E)
                {
                    cLogManager.LogException(E);
                }
                return false;
            }

        }

        public class PermissionLink : cMsGraphResultPermission
        {
            public string Scope { get; private set; } = null;
            public string Type { get; private set; } = null;
            public string PreventsDownload { get; private set; } = null;
            public string WebUrl { get; private set; } = null;

            public PermissionLink(cMsGraphResultFileObject Item, cMsGraphResultBase Result) :
                base(Item, Result)
            {
                try
                {
                    if (Result.Result.TryGetValue("link", out JToken JT1))
                    {
                        dynamic lnk = Result.Result.link;
                        if (lnk.TryGetValue("type", out JToken JT2))
                            Type = lnk.type;
                        if (lnk.TryGetValue("scope", out JToken JT3))
                            Scope = lnk.scope;
                        if (lnk.TryGetValue("preventsDownload", out JToken JT4))
                            PreventsDownload = lnk.preventsDownload;
                        if (lnk.TryGetValue("webUrl", out JToken JT5))
                            WebUrl = lnk.webUrl;
                    }
                    if (Result.Result.TryGetValue("grantedToIdentities", out JToken JT6))
                    {
                        Identities.Clear();
                        foreach (dynamic Entry in Result.Result.grantedToIdentities)
                        {
                            try
                            {
                                var Ident = new cMsGraphPermissionIdentity(Entry);
                                if (Ident != null)
                                    Identities.Add(Ident);

                            }
                            catch (Exception E)
                            {
                                cLogManager.LogException(E);
                            }
                        }
                    }
                }
                catch (Exception E)
                {
                    cLogManager.LogException(E);
                }

            }

            public static bool Check(dynamic Result)
            {
                try
                {
                    if (Result.TryGetValue("link", out JToken JT1))
                        return true;
                }
                catch (Exception E)
                {
                    cLogManager.LogException(E);
                }
                return false;
            }

        }

        public class cMsGraphPermissionIdentity
        {
            public enum eIdentityType { unknown = 0, user, device, application, };

            public eIdentityType IdentityType { get; private set; } = eIdentityType.unknown;
            public string ID { get; private set; } = null;
            public string EMail { get; private set; } = null;
            public string DisplayName { get; private set; } = null;

            public cMsGraphPermissionIdentity(dynamic Result)
            {
                try
                {
                    dynamic ident = null;

                    if (Result.TryGetValue("user", out JToken JT1))
                    {
                        ident = Result.user;
                        IdentityType = eIdentityType.user;
                    }
                    else if (Result.TryGetValue("device", out JToken JT2))
                    {
                        ident = Result.device;
                        IdentityType = eIdentityType.device;
                    }
                    else if (Result.TryGetValue("application", out JToken JT3))
                    {
                        ident = Result.application;
                        IdentityType = eIdentityType.application;
                    }

                    if (ident != null)
                        if (ident.TryGetValue("displayName", out JToken JT3))
                            DisplayName = ident.displayName;
                    if (ident.TryGetValue("id", out JToken JT4))
                        ID = ident.id;
                    if (ident.TryGetValue("email", out JToken JT5))
                        EMail = ident.email;
                }
                catch (Exception E)
                {
                    cLogManager.LogException(E);
                }
            }
        }

    }

    public class cMsGraphCollectionPermissions : List<cMsGraphResultPermission>
    {
        public cMsGraphCollectionPermissions() { }
        public cMsGraphCollectionPermissions(int n) : base(n) { }

    }

    public class cMsGraphResultFileObject : cMsGraphResultBase
    {
        public readonly string DriveID = null;

        public readonly cMsGraphResultDrive Drive = null;

        public bool IsFolder { get; private set; } = false;

        public string WebUrl { get; private set; } = null;

        public cMsGraphCollectionFileObjects Folders { get; private set; } = null;
        public cMsGraphCollectionFileObjects Files { get; private set; } = null;

        public cMsGraphCollectionPermissions Permissions { get; private set; } = null;

        public cMsGraphResultFileObject(string DriveID, cMsGraphResultBase Result, cMsGraphResultDrive Drive = null) : base(Result)
        {
            this.DriveID = DriveID;
            this.Drive = Drive;

            try
            {
                IsFolder = !Result.Result.TryGetValue("file", out JToken JT1);
                WebUrl = cMsGraphResultBase.GetStringFromDynamic(Result.Result, "webUrl");
            }
            catch { }
        }

        public async Task<bool> ResolveFolders(cMsGraphSharepoint SP)
        {
            Folders = await SP.RequestFileObjectSubitems(this.DriveID, this.ID, true, this.Drive);

            return Folders != null;
        }

        public async Task<bool> ResolveFiles(cMsGraphSharepoint SP)
        {
            Files = await SP.RequestFileObjectSubitems(this.DriveID, this.ID, true, this.Drive);

            return Files != null;
        }

        public async Task<bool> ResolvePermissions(cMsGraphSharepoint SP)
        {
            Permissions = await SP.RequestFileObjectPermissions(this);

            return Permissions != null;
        }

        public async Task<List<cMsGraphResultPermission>> AddPermission(cMsGraphSharepoint SP, IEnumerable<cMsGraphResultPermission.ePermissionRole> Roles, cMsGraphResultPermission.eDriveReceipientType ReceipientType , string RecipientID, bool RequireSignIn = true, bool SendInvitation = false, string Message = null)
        {
            try
            {
                var RetVal = await SP.AddFileObjectPermission(this, Roles, ReceipientType, RecipientID, RequireSignIn, SendInvitation, Message);
                return RetVal;
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }

            return null;
        }

        public async Task<bool> RemovePermission(cMsGraphSharepoint SP, cMsGraphResultPermission.ePermissionRole Role, string RecipientID)
        {
            try
            {
                var RetVal = await ResolvePermissions(SP);
                if (!RetVal || Permissions == null)
                    return true;

                var Found = false;
                foreach (var Entry in Permissions)
                {
                    if (Entry.Identities == null || Entry.Roles == null)
                        continue;

                    if (!Entry.Roles.Contains(Role))
                        continue;
                    
                    foreach (var Identity in Entry.Identities)
                    {
                        var Revoked = false;
                        if (Identity.ID == RecipientID)
                            Revoked = await Entry.RevokeGrant(SP, cMsGraphResultPermission.eDriveReceipientType.objectId, RecipientID);
                        else if (Identity.EMail?.ToUpperInvariant() == RecipientID.ToUpperInvariant())
                            Revoked = await Entry.RevokeGrant(SP, cMsGraphResultPermission.eDriveReceipientType.email, RecipientID);

                        Found |= Revoked;
                    }
                }

                return Found;
            }
            catch (Exception E)
            {
                cLogManager.LogException(E);
            }

            return false;
        }

    }

    public class cMsGraphCollectionFileObjects : Dictionary<string, cMsGraphResultFileObject>
    {
        public cMsGraphCollectionFileObjects() { }
        public cMsGraphCollectionFileObjects(int n) : base(n) { }

        public void Add(cMsGraphResultFileObject FileObject)
        {
            if (!this.ContainsKey(FileObject.ID))
                this.Add(FileObject.ID, FileObject);
        }
    }

    public class cMsGraphResultChannelRoot : cMsGraphResultBase
    {
        public cMsGraphResultChannel Channel { get; private set; } = null;
        public cMsGraphResultDrive Drive { get; private set; } = null;

        public string DriveID { get; private set; } = null;

        public cMsGraphResultChannelRoot(cMsGraphResultChannel Channel, cMsGraphResultBase Result) : base(Result)
        {
            this.Channel = Channel;
            try
            {
                if (Result.Result.TryGetValue("parentReference", out JToken JT1))
                {
                    var pR = Result.Result.parentReference;
                    if (pR.TryGetValue("driveId", out JToken JT2))
                    {
                        DriveID = pR.driveId;
                    }
                }
            }
            catch
            {
            }
        }

        public async Task<bool> ResolveDrive(cMsGraphSharepoint SP)
        {
            if (string.IsNullOrEmpty(DriveID))
                return false;

            Drive = await SP.RequestDriveInfo(DriveID);

            return Drive != null;
        }

    }
}
