using System;
using System.ServiceModel;
using System.ServiceModel.Description;

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Crm.Sdk.Messages;

using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Sdk.Messages;

using System.Collections.Generic;
using System.DirectoryServices;
using System.Threading;

namespace VRM.VA.UserProvision.Plugin
{   
    public class UserProvisionPostStage : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            ITracingService tracingService =
                (ITracingService)serviceProvider.GetService(typeof(ITracingService));

            IPluginExecutionContext context = (IPluginExecutionContext)
              serviceProvider.GetService(typeof(IPluginExecutionContext));

            IOrganizationService OrganizationService = null;
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            OrganizationService = serviceFactory.CreateOrganizationService(context.UserId);

            if (!context.InputParameters.Contains("Target") ||
                    !(context.InputParameters["Target"] is Entity)) return;
            
            var entity = (Entity)context.InputParameters["Target"];
            tracingService.Trace("entity set");

            string outMessage = string.Empty;
            bool fError = false;
            bool fWarning = false;

            Guid outUserID = Guid.Empty;

            try
            {
                Guid businessUnit = Guid.Empty;
                
                // Only running on create, so all needed fields SHOULD be included in the context
                tracingService.Trace("Reached first try method");

                // Verify that the target entity represents the correct entity
                // If not, this plug-in was not registered correctly.
                if (entity.LogicalName != "vhacrm_userprovision") return;
                
                tracingService.Trace("Entity is of type: " + entity.LogicalName);

                // Verify updated/inserted record contains the fields we need
                if (entity.Attributes.Contains("vhacrm_firstname_text") && entity.Attributes.Contains("vhacrm_lastname_text") 
                    && entity.Attributes.Contains("vhacrm_name") && entity.Attributes.Contains("vhacrm_userprovisionroleid"))
                {
                    // Get the entity reference
                    EntityReference UserProvisionRoleRef = (EntityReference)entity.Attributes["vhacrm_userprovisionroleid"];
                    outMessage += "Related User Provision Role (" + UserProvisionRoleRef.Id.ToString() + ") found" + Environment.NewLine;

                    // Get the related User Provision Role record
                    Entity UserProvisionRoleEntity = PluginHelper.GetEntity("vhacrm_userprovisionrole", "vhacrm_name,vhacrm_teams_text,vhacrm_roles_text,vhacrm_businessunitid", UserProvisionRoleRef.Id, OrganizationService, tracingService);

                    // Error if not found
                    if (UserProvisionRoleEntity != null && UserProvisionRoleEntity.Attributes.Contains("vhacrm_name"))
                    {
                        outMessage += "Related User Provision Role Entity retrieved" + Environment.NewLine;
                        // Check the operation type: Add or Remove
                        bool isAdd = true;
                        if (entity.Attributes.Contains("vhacrm_action_code"))
                        {
                            isAdd = ((OptionSetValue)entity.Attributes["vhacrm_action_code"]).Value == 713770000 ? true : false;
                        }
                        outMessage += "Record operation is: " + (isAdd ? "Add" : "Remove") + Environment.NewLine;

                        // See if the user exists (by email)
                        outMessage += "Checking for existing User Records with address: " + entity.Attributes["vhacrm_name"].ToString() + Environment.NewLine;
                        EntityCollection userEntities = PluginHelper.GetUsersByEmail(entity.Attributes["vhacrm_name"].ToString(), OrganizationService, tracingService);
                        Entity userEntity = new Entity("systemuser");

                        if (UserProvisionRoleEntity.Attributes.Contains("vhacrm_businessunitid"))
                        {
                            businessUnit = ((EntityReference)UserProvisionRoleEntity["vhacrm_businessunitid"]).Id;
                            outMessage += "User Provision Role has Business Unit ID: " + businessUnit.ToString() + Environment.NewLine;
                        }

                        // Create the user if needed
                        if (userEntities != null && userEntities.Entities.Count > 0)
                        {
                            // Always take the first one if there is more than one, sorted by created date descending, so we get the newest one
                            userEntity = userEntities[0];
                            outUserID = userEntity.Id;

                            outMessage += "Existing User Record found with ID: " + userEntity.Id.ToString() + Environment.NewLine;
                            if (userEntity.Attributes["isdisabled"].ToString() == "True")
                            {
                                if (userEntity.Attributes.Contains("bah_aadisabled") && userEntity.Attributes["bah_aadisabled"].ToString() == "True")
                                {
                                    outMessage += "WARNING - The user has been diabled due to inactivity. No further action taken." + Environment.NewLine;
                                    outMessage += "Completed at " + DateTime.Now.ToString() + Environment.NewLine;
                                    fWarning = true;
                                    goto CloseProvisionRecord;
                                }
                                else
                                {
                                    outMessage += "User is disabled, enabling user" + Environment.NewLine;
                                    SetStateRequest request = new SetStateRequest()
                                    {
                                        EntityMoniker = userEntity.ToEntityReference(),
                                        // Sets the user to enabled.
                                        State = new OptionSetValue(0),
                                        // Required by request but always valued at -1 in this context.
                                        Status = new OptionSetValue(-1)
                                        /*
                                        //Sets the user to disabled.
                                        State = new OptionSetValue(1),
                                        // Required by request but always valued at -1 in this context.
                                        Status = new OptionSetValue(-1)    
                                        */
                                    };

                                    OrganizationService.Execute(request);
                                }
                            }
                        }
                        else
                        {
                            outMessage += "Creating new user" + Environment.NewLine;

                            userEntity.Attributes["firstname"] = entity["vhacrm_firstname_text"].ToString();
                            userEntity.Attributes["lastname"] = entity["vhacrm_lastname_text"].ToString();
                            userEntity.Attributes["domainname"] = entity["vhacrm_name"].ToString();
                            userEntity.Attributes["internalemailaddress"] = entity["vhacrm_emailaddress_text"].ToString();

                            // Only set the Business Unit if it was supplied
                            if (businessUnit != Guid.Empty)
                            {
                                userEntity.Attributes["businessunitid"] = (EntityReference)UserProvisionRoleEntity["vhacrm_businessunitid"];
                                outMessage += "Setting Business Unit: " + PluginHelper.GetFieldValue(businessUnit, "businessunit", "name", OrganizationService, tracingService) + Environment.NewLine;
                            }

                            outUserID = OrganizationService.Create(userEntity);
                            userEntity.Id = outUserID;

                            outMessage += "Created User Record with ID: " + outUserID + Environment.NewLine;
                        }                        
                        // Create or deactivate the Related User Role record
                        if (isAdd)
                        {
                            // Do they already have a Related User Role record for this User Provision Role
                            EntityCollection userRoles = PluginHelper.GetUserRoles(outUserID, UserProvisionRoleRef.Id, OrganizationService, tracingService);

                            // If there are none, create one
                            if (userRoles.Entities.Count == 0)
                            {
                                outMessage += "Creating related User Role record" + Environment.NewLine;
                                Entity userRole = new Entity("vhacrm_userrole");
                                userRole.Attributes["vhacrm_systemuserid"] = userEntity.ToEntityReference();
                                userRole.Attributes["vhacrm_userprovisionroleid"] = UserProvisionRoleRef;

                                OrganizationService.Create(userRole);
                            }
                            else
                            {
                                outMessage += "A User Role record was found for the Role, a new one was not created. " + Environment.NewLine;
                            }
                        }
                        else
                        {
                            outMessage += "Deactivating related User Role record" + Environment.NewLine;
                            // Get the role
                            EntityCollection userRoles = PluginHelper.GetUserRoles(outUserID, UserProvisionRoleRef.Id, OrganizationService, tracingService);
                            outMessage += "Number of records found: " + userRoles.Entities.Count.ToString() + Environment.NewLine;
                            if (userRoles.Entities.Count > 0)
                            {
                                // Change the state/status to deactivated
                                var setUserRoleStateRequest = new SetStateRequest
                                {
                                    EntityMoniker = new EntityReference("vhacrm_userrole", userRoles.Entities[0].Id),
                                    State = new OptionSetValue(1),
                                    Status = new OptionSetValue(-1)
                                };

                                OrganizationService.Execute(setUserRoleStateRequest);
                            }
                        }

                        // Loop to Add or Remove the Roles
                        if (UserProvisionRoleEntity.Attributes.Contains("vhacrm_roles_text"))
                        {
                            outMessage += "Parsing Roles" + Environment.NewLine;
                            string[] parsedRoles = UserProvisionRoleEntity["vhacrm_roles_text"].ToString().Split(',');
                            
                            // Get the business unit of the user in case it is a parent of the user provision one
                            outMessage += "Retrieving User Business Unit" + Environment.NewLine;
                            businessUnit = ((EntityReference)userEntity.Attributes["businessunitid"]).Id;
                            outMessage += "User has Business Unit ID: " + businessUnit.ToString() + Environment.NewLine;

                            for (int i = 0; i < parsedRoles.Length; i++)
                            {
                                // Check for System Administrator Role and skip if it is
                                if (parsedRoles[i] != "System Administrator")
                                {
                                    // Retrieve the role ID
                                    outMessage += "Retrieving Role: " + parsedRoles[i] + Environment.NewLine;

                                    EntityCollection roleEntities = PluginHelper.GetRolesByNameUnit(parsedRoles[i], businessUnit, OrganizationService, tracingService);
                                    if (roleEntities != null && roleEntities.Entities.Count > 0)
                                    {
                                        try
                                        {
                                            if (isAdd)
                                            {
                                                // Add the role to the user
                                                OrganizationService.Associate(
                                                           "systemuser",
                                                           outUserID,
                                                           new Relationship("systemuserroles_association"),
                                                           new EntityReferenceCollection() { new EntityReference(roleEntities[0].LogicalName, roleEntities[0].Id) });

                                                outMessage += "Added role: " + parsedRoles[i] + ", id: " + roleEntities[0].Id.ToString() + Environment.NewLine;
                                            }
                                            else
                                            {
                                                // Check the role against the required roles
                                                if (!PluginHelper.IsRoleRequiredByUser(parsedRoles[i], outUserID, OrganizationService, tracingService))
                                                {
                                                    OrganizationService.Disassociate(
                                                               "systemuser",
                                                               outUserID,
                                                               new Relationship("systemuserroles_association"),
                                                               new EntityReferenceCollection() { new EntityReference(roleEntities[0].LogicalName, roleEntities[0].Id) });

                                                    outMessage += "Removed role: " + parsedRoles[i] + ", id: " + roleEntities[0].Id.ToString() + Environment.NewLine;
                                                }
                                                else
                                                {
                                                    outMessage += "Role is required by user and was not removed: " + parsedRoles[i] + ", id: " + roleEntities[0].Id.ToString() + Environment.NewLine;
                                                }
                                            }
                                        }
                                        catch (Exception ex)
                                        {                                           
                                            if (ex.Message.ToString() == "Cannot insert duplicate key.")
                                            {
                                                outMessage += "User already has role" + Environment.NewLine;
                                            }
                                            else
                                            {
                                                outMessage += "ERROR - Unable to process request: " + ex.Message.ToString() + Environment.NewLine;
                                                fError = true;
                                            }
                                        }
                                    }
                                    else
                                    {
                                        outMessage += "ERROR - Unable to find role: " + parsedRoles[i] + Environment.NewLine;
                                        fError = true;
                                    }
                                    
                                }
                            }
                        }

                        // Loop to Add or Remove the Teams
                        if (UserProvisionRoleEntity.Attributes.Contains("vhacrm_teams_text"))
                        {
                            outMessage += "Parsing Teams" + Environment.NewLine;
                            string[] parsedTeams = UserProvisionRoleEntity["vhacrm_teams_text"].ToString().Split(',');

                            for (int i = 0; i < parsedTeams.Length; i++)
                            {
                                // Retrieve the team ID
                                outMessage += "Retrieving team: " + parsedTeams[i] + Environment.NewLine;

                                EntityCollection teamEntities = PluginHelper.GetTeamsByName(parsedTeams[i], OrganizationService, tracingService);
                                if (teamEntities != null && teamEntities.Entities.Count > 0)
                                {
                                    // TODO: look to combine and remove duplicated lines between add and remove. Trick is initializing the request object
                                    try
                                    {
                                        if (isAdd)
                                        {
                                            // Add or remove the team to the user
                                            AddMembersTeamRequest req = new AddMembersTeamRequest();
                                            req.TeamId = teamEntities[0].Id;
                                            Guid[] aryMembers = new Guid[1];
                                            aryMembers[0] = outUserID;
                                            req.MemberIds = aryMembers;
                                            OrganizationService.Execute(req);

                                            outMessage += "Added team: " + parsedTeams[i] + ", id: " + teamEntities[0].Id.ToString() + Environment.NewLine;
                                        }
                                        else
                                        {
                                            // Check the team against the required teams
                                            if (!PluginHelper.IsTeamRequiredByUser(parsedTeams[i], outUserID, OrganizationService, tracingService))
                                            {
                                                // Remove the team from the user
                                                RemoveMembersTeamRequest req = new RemoveMembersTeamRequest();
                                                req.TeamId = teamEntities[0].Id;
                                                Guid[] aryMembers = new Guid[1];
                                                aryMembers[0] = outUserID;
                                                req.MemberIds = aryMembers;
                                                OrganizationService.Execute(req);

                                                outMessage += "Removed team: " + parsedTeams[i] + ", id: " + teamEntities[0].Id.ToString() + Environment.NewLine;
                                            }
                                            else
                                            {
                                                outMessage += "Team is required by user and was not removed: " + parsedTeams[i] + ", id: " + teamEntities[0].Id.ToString() + Environment.NewLine;
                                            }                                                                                    
                                        }
                                    }
                                    catch (Exception ex)
                                    {
                                        outMessage += "ERROR - Unable to process request: " + ex.Message.ToString() + Environment.NewLine;
                                        fError = true;
                                    }
                                }
                                else
                                {
                                    outMessage += "ERROR - Unable to find team: " + parsedTeams[i] + Environment.NewLine;
                                    fError = true;
                                }
                            }
                        }

                        outMessage += "Successfully completed at " + DateTime.Now.ToString() + Environment.NewLine;
                    }
                    else
                    {
                        outMessage += "ERROR - Unable to retrieve User Provision Role Record" + Environment.NewLine;
                        fError = true;
                    } 
                }
                else
                {
                    outMessage += "ERROR - Required fields missing on User Provision Record" + Environment.NewLine;
                    fError = true;
                }                
            }
            catch (FaultException<OrganizationServiceFault> ex)
            {
                outMessage += "ERROR - " + ex.ToString() + Environment.NewLine;
                fError = true;
            }
            catch (Exception ex)
            {
                outMessage += "ERROR - " + ex.ToString() + Environment.NewLine;
                fError = true;
            }

        CloseProvisionRecord:

            // Update the source record with the message and deactivate REQUIRES plugin to be ASYNC
            try
            {
                outMessage += "Updating Source Record" + Environment.NewLine;

                // Set the note to the output message
                entity["vhacrm_message_memo"] = outMessage;
                // Set the user lookup to the user created/updated
                if (outUserID != Guid.Empty)
                {
                    outMessage += "Relating User" + Environment.NewLine;
                    // Set the entity reference
                    entity["vhacrm_systemuserid"] = new EntityReference("systemuser", outUserID);                 
                }
                OrganizationService.Update(entity);

                int statusReason = 2;
                if (fError) { statusReason = 713770000; }
                else if (fWarning) { statusReason = 713770001; }

                // Change the state/status to deactivated
                var setStateRequest = new SetStateRequest
                {
                    EntityMoniker = new EntityReference("vhacrm_userprovision", entity.Id),
                    State = new OptionSetValue(1),
                    Status = new OptionSetValue(statusReason)
                };

                OrganizationService.Execute(setStateRequest);
            }
            catch (Exception ex)
            {
                tracingService.Trace(outMessage);
                PluginHelper.CreateAndThrowError("Error in Plugin: " + ex.Message, tracingService);
            }

        }
    }
}
