using System;
using Telerik.Sitefinity.Data.ContentLinks;
using Telerik.Sitefinity.DynamicModules.Model;
using Telerik.Sitefinity.Model;
using Telerik.Sitefinity.Publishing;
using Telerik.Sitefinity.SiteSync;
using Telerik.Sitefinity.Utilities.TypeConverters;

namespace SitefinityWebApp
{
    public class CustomMigrationBehavior : ICustomMigrationBehavior
    {
        /// <summary>
        /// Gets a value indicating whether implicitly syncing item dependencies should be skipped
        /// </summary>
        public bool SkipLoadingDependencies
        {
            get { return false; }
        }

        /// <summary>
        /// Prepares an item for migration
        /// </summary>
        /// <param name="item">The exported item</param>
        /// <param name="obj">The wrapper object containing exported information</param>
        /// <param name="context">The export context</param>
        public void ProcessItemExport(object item, WrapperObject obj, ISiteSyncExportContext context)
        {
            if (item is DynamicContent dynamicContent)
            {
                // Example on how to tell the destination server to create new item with different ID.
                // All properties of the WrapperObject could be altered here.
                this.MapForceCreateItem(dynamicContent, obj, context);
            }
        }

        /// <summary>
        /// Validates the import operation
        /// </summary>
        /// <param name="wrapperObject">The wrapper object containing import information</param>
        /// <param name="importTransaction">The import transaction</param>
        /// <returns>A value indicating whether the import operation is valid</returns>
        public bool ProcessItemImport(WrapperObject wrapperObject, ISiteSyncImportTransaction importTransaction)
        {
            var itemType = wrapperObject.GetPropertyOrDefault<string>(ObjectTypeId);
            var type = TypeResolutionService.ResolveType(itemType, false);

            if (type != null && typeof(DynamicContent).IsAssignableFrom(type))
            {
                // Example on how to change the "OriginalContentId" property to match the new item ID
                this.Map(importTransaction.Response.Mappings, wrapperObject, "OriginalContentId");
            }

            return true;
        }

        /// <summary>
        /// Handles the logic on the target server after migration completes
        /// </summary>
        /// <param name="migrationMappings">The migration mappings</param>
        /// <param name="siteInfo">The target site info</param>
        public void HandleMigrationCompleted(SiteSyncMigrationMappings migrationMappings, SiteSyncSiteInfo siteInfo)
        {
            var linksManager = ContentLinksManager.GetManager();
            var links = linksManager.GetContentLinks();
            foreach (var link in links)
            {
                this.Map(migrationMappings, link, "ChildItemId", link.ChildItemType);
                this.Map(migrationMappings, link, "ParentItemId", link.ParentItemType);
            }

            linksManager.SaveChanges();
        }

        #region Helper methods

        private void Map(SiteSyncMigrationMappings migrationMappings, object obj, string propertyName, string mapType = null, string mappedPropertyName = ItemId)
        {
            if (obj is WrapperObject wrapperObject)
            {
                var itemType = wrapperObject.GetPropertyOrDefault<string>(ObjectTypeId);
                if (wrapperObject.HasProperty(propertyName))
                {
                    var mapping = migrationMappings.GetMapping(mapType ?? itemType, wrapperObject.GetProperty(propertyName).ToString(), mappedPropertyName);
                    if (mapping != null)
                    {
                        var currentProp = wrapperObject.GetPropertyOrNull(propertyName);
                        if (currentProp != null && currentProp.GetType() == typeof(Guid))
                        {
                            wrapperObject.SetProperty(propertyName, Guid.Parse(mapping));
                        }
                        else
                        {
                            wrapperObject.SetProperty(propertyName, mapping);
                        }
                    }
                }
            }
            else
            {
                var itemType = obj.GetType();
                var property = itemType.GetProperty(propertyName);
                if (property != null)
                {
                    var mapping = migrationMappings.GetMapping(mapType ?? itemType.FullName, property.GetValue(obj, null).ToString(), mappedPropertyName);
                    if (mapping != null)
                    {
                        if (property.PropertyType == typeof(Guid))
                        {
                            property.SetValue(obj, Guid.Parse(mapping));
                        }
                        else
                        {
                            property.SetValue(obj, mapping);
                        }
                    }
                }
            }
        }

        private void MapForceCreateItem(IDataItem dataItem, WrapperObject obj, ISiteSyncExportContext context)
        {
            var mapping = context.GetMapping(dataItem.GetType().FullName, dataItem.Id);
            if (mapping == null)
            {
                // The item is not existing on the target server and it should be created even if the item with same id already exists.
                obj.AddProperty(ForceCreateNewItem, true);
            }
            else
            {
                var newId = (Guid)mapping;
                // The item is already created on the target server.
                obj.AddProperty(Id, newId); // The identity of the item can't be changed, so we add the Id as additional property.
                obj.SetOrAddProperty(ObjectId, newId);
            }
        }

        #endregion

        private const string Id = "Id";
        private const string ItemId = "ItemId";
        private const string ObjectId = "objectId";
        private const string ObjectTypeId = "objectTypeId";
        private const string ForceCreateNewItem = "ForceCreateNewItem";
    }
}