#KaskoPolicyModificationSetRootModelExtension.cs

        private void FillKaskoModificationBasketModel(IntegrationViewModel viewModel, KaskoModificationBasketModel model)
        {
            var vmDict = viewModel.GetDictionary();

            vmDict.TryGetValue(nameof(KaskoModificationBasketModel.ParentPolicyNumber), out var policyNumber);
            vmDict.TryGetValue(nameof(KaskoModificationBasketModel.Deal), out var deal);
            vmDict.TryGetValue(nameof(KaskoModificationBasketModel.PaymentSchedule), out var paymentSchedule);

            model.ParentPolicyNumber = policyNumber?.ToString();
            if (deal is KaskoModificationViewModel_Deal dealObject)
            {
                model.Deal = new KaskoModificationDealModel();
                FillKaskoModificationDealModel(dealObject, model);
            }

            if (paymentSchedule is List<KaskoModificationViewModel_PaymentSchedule> paymentScheduleList)
            {
                model.PaymentSchedule = new List<KaskoModificationPaymentScheduleModel>();

                foreach (var paymentScheduleItem in paymentScheduleList)
                {
                    var paymentScheduleModel = new KaskoModificationPaymentScheduleModel();
                    FillKaskoModificationPaymentScheduleModel(paymentScheduleItem, paymentScheduleModel);
                    model.PaymentSchedule.Add(paymentScheduleModel);
                }
            }
        }
		
		        private void FillKaskoModificationDealModel(KaskoModificationViewModel_Deal dealObject, KaskoModificationBasketModel model)
        {
            var dealDict = dealObject.GetDictionary();

            dealDict.TryGetValue(nameof(KaskoModificationDealModel.IsMultidrive), out var isMultidriveVM);
            dealDict.TryGetValue(nameof(KaskoModificationDealModel.ChangeDate), out var changeDateVM);
            dealDict.TryGetValue(nameof(KaskoModificationDealModel.Auto), out var autoVM);
            dealDict.TryGetValue(nameof(KaskoModificationDealModel.Drivers), out var driverVM);
            dealDict.TryGetValue(nameof(KaskoModificationDealModel.Insurant), out var insurantVM);
            dealDict.TryGetValue(nameof(KaskoModificationDealModel.Owner), out var ownerVM);
            dealDict.TryGetValue(nameof(KaskoModificationDealModel.MinimalAge), out var minimalAgeVM);
            dealDict.TryGetValue(nameof(KaskoModificationDealModel.MinimalDrivingExperience), out var minimalDrivingExperienceVM);
            dealDict.TryGetValue(nameof(KaskoModificationDealModel.Packet), out var packetVM);

            if (isMultidriveVM is bool isMultidrive)
            {
                model.Deal.IsMultidrive = isMultidrive;
            }

            if (changeDateVM is DateTime changeDate)
            {
                model.Deal.ChangeDate = changeDate;
            }

            if (minimalAgeVM is int minimalAge)
            {
                model.Deal.MinimalAge = minimalAge;
            }

            if (minimalDrivingExperienceVM is int minimalDrivingExperience)
            {
                model.Deal.MinimalDrivingExperience = minimalDrivingExperience;
            }

            if (autoVM is KaskoModificationViewModel_Auto auto)
            {
                model.Deal.Auto = new KaskoModificationAutoModel();
                FillKaskoModificationAutoModel(auto, model.Deal.Auto);
            }

            if (driverVM is List<KaskoModificationViewModel_Driver> drivers)
            {
                model.Deal.Drivers = new List<KaskoModificationDriverModel>();
                FillKaskoModificationDriversModel(drivers, model);
            }

            if (insurantVM is KaskoModificationViewModel_Insurant insurant)
            {
                model.Deal.Insurant = new KaskoModificationInsurantModel();
                FillKaskoModificationInsurantModel(insurant, model);
            }

            if (ownerVM is KaskoModificationViewModel_Owner owner)
            {
                model.Deal.Owner = new KaskoModificationOwnerModel();
                FillKaskoModificationOwnerModel(owner, model);
            }

            if (packetVM is KaskoModificationViewModel_Packet packet)
            {
                model.Deal.Packet = new KaskoModificationPacketModel();
                FillKaskoModificationPacketModel(packet, model);
            }
        }
		
		        private void FillKaskoModificationInsurantModel(KaskoModificationViewModel_Insurant insurant, KaskoModificationBasketModel model)
        {
            insurant.GetDictionary().TryGetValue(nameof(KaskoModificationOwnerModel.Person), out var personVM);
            if (personVM is KaskoModificationViewModel_Person person)
            {
                model.Deal.Insurant.Person = new KaskoModificationPersonModel();
                FillKaskoModificationPersonModel(person, model.Deal.Insurant.Person);
            }
        }
		
		        private void FillKaskoModificationPersonModel(KaskoModificationViewModel_Person person, KaskoModificationPersonModel model)
        {
            var personDict = person.GetDictionary();

            personDict.TryGetValue(nameof(KaskoModificationPersonModel.LastName), out var lastNameVM);
            personDict.TryGetValue(nameof(KaskoModificationPersonModel.Name), out var nameVM);
            personDict.TryGetValue(nameof(KaskoModificationPersonModel.MiddleName), out var middleNameVM);
            personDict.TryGetValue(nameof(KaskoModificationPersonModel.Email), out var emailVM);
            personDict.TryGetValue(nameof(KaskoModificationPersonModel.IsGettingMarried), out var isGettingMarriedVM);
            personDict.TryGetValue(nameof(KaskoModificationPersonModel.DateOfBirth), out var dateOfBirthVM);
            personDict.TryGetValue(nameof(KaskoModificationPersonModel.Addresses), out var addressesVM);
            personDict.TryGetValue(nameof(KaskoModificationPersonModel.IdentityDocument), out var identityDocVM);
            personDict.TryGetValue(nameof(KaskoModificationPersonModel.Inn), out var innVM);

            model.Name = nameVM?.ToString().Capitalize();
            model.LastName = lastNameVM?.ToString().Capitalize();
            model.MiddleName = middleNameVM?.ToString().Capitalize();
            model.Email = emailVM?.ToString();
            model.Inn = innVM?.ToString();
            
            if (dateOfBirthVM is DateTime dateOfBirth)
            {
                model.DateOfBirth = dateOfBirth;
            }

            if (isGettingMarriedVM is bool isGettingMarried)
            {
                model.IsGettingMarried = isGettingMarried;
            }

            if (addressesVM is List<KaskoModificationViewModel_Address> addresses)
            {
                FillKaskoModificationAddressesModel(addresses, model);
            }

            if (identityDocVM is KaskoModificationViewModel_IdentityDocument identityDoc)
            {
                FillKaskoModificationIdentityDocumentModel(identityDoc, model);
            }
        }
		
#KaskoModificationProcedures.cs

        public void UpdateBasket(Basket basket, KaskoModificationBasketModel update)
        {
            if (!basket.EntityContext.ContainsKey(nameof(KaskoModificationBasketModel)))
            {
                basket.EntityContext.Add(nameof(KaskoModificationBasketModel), update);
            }

            var deal = basket.GetKaskoDeal();
            if (deal != null)
            {
                if (update.Deal == null)
                {
                    return;
                }

                var updateDeal = update.Deal;
                var updateOwner = updateDeal.Owner?.Person;
                var updateInsurant = updateDeal.Insurant?.Person;
                var updateAuto = updateDeal.Auto;
                var updateTsIdentityCard = updateDeal.Auto?.TsIdentityCard;
                var updateDrivers = updateDeal.Drivers;

                deal.AutoDeal.IsMultidrive = updateDeal.IsMultidrive ?? deal.AutoDeal.IsMultidrive;
                deal.ChangesStartDate = updateDeal.ChangeDate;
                deal.AutoDeal.DriverMinAge = updateDeal.MinimalAge ?? deal.AutoDeal.DriverMinAge;
                deal.AutoDeal.DriverMinExperience = updateDeal.MinimalDrivingExperience ?? deal.AutoDeal.DriverMinExperience;

                //deal.SuggestedBanks.FirstOrDefault().Name = updateDeal.Auto.BankOfCredit ?? deal.SuggestedBanks?.FirstOrDefault()?.Name;
                //deal.SuggestedBanks.FirstOrDefault().BIK = updateDeal.Auto.BankOfCreditBik ?? deal.SuggestedBanks?.FirstOrDefault()?.BIK;
                var insurant = deal.Insurant;
                var owner = deal.GetOwner();
                var drivers = deal.AutoDeal.Drivers;
                var auto = deal.GetAuto();
                var tsIdentityCard = deal.AutoDeal?.TsIdentityCard;

                UpdateOwner(updateOwner, owner);
                UpdateInsurant(updateInsurant, insurant);
                UpdateDrivers(updateDrivers, drivers);
                UpdateAuto(updateAuto, auto);
                UpdateTsIdentityCard(updateTsIdentityCard, tsIdentityCard);

                var autoDictionaryBasketUpdater = ServiceProvider.GetRequiredService<IAutoDictionaryBasketUpdater>();
                autoDictionaryBasketUpdater.UpdateAutoDictionaryValues(basket);
            }
        }
		
		        private void UpdateInsurant(KaskoModificationPersonModel updateInsurant, Insurant insurant)
        {
            var person = insurant.GetPerson();

            UpdatePerson(updateInsurant, person);
            UpdateIdentityDocument(updateInsurant?.IdentityDocument, insurant.IdentityDocument);
        }
		
		        private void UpdatePerson(KaskoModificationPersonModel updatePerson, Person person)
        {
            if (updatePerson != null)
            {
                person.LastName = updatePerson.LastName ?? person.LastName;
                person.Name = updatePerson.Name ?? person.Name;
                person.MiddleName = updatePerson.MiddleName ?? person.MiddleName;
                person.Email1 = updatePerson.Email ?? person.Email1;
                person.DateOfBirth = updatePerson.DateOfBirth ?? person.DateOfBirth;
                person.Age = updatePerson.DateOfBirth != null ? DurationHelper.CalculateAge(updatePerson.DateOfBirth) : person.Age;
                person.InnNumber = updatePerson.Inn ?? person.InnNumber;

                if (updatePerson.Addresses != null)
                {
                    UpdateAddresses(person.Addresses, updatePerson.Addresses);
                }
            }
        }
		
#ObjectComparerBuilder

using System;
using System.Collections;
using System.Linq;
using iRIS.Framework.Data.Interfaces;
using iRIS.Framework.Entity.Metadata.Manager.Interfaces;
using iRIS.InsuranceCore.Code.Implementations.Comparison;
using iRIS.InsuranceCore.Code.Interfaces;
using iRIS.InsuranceCore.Entities;
using iRIS.InsuranceCore.Infrastructure.Constants;
using Microsoft.Extensions.DependencyInjection;

namespace iRIS.KaskoLink2.Modification.Code.Implementations.Comparison
{
    public static class ObjectComparerBuilder
    {
        public static Interfaces.Comparison.IObjectComparer<Deal> CreateDealComparer(IServiceProvider serviceProvider)
        {
            var pathManager = serviceProvider.GetService<IPathManager>();
            var propertyIterator = serviceProvider.GetService<IPropertyIterator>();

            var collectionItemIdentityComparer = new CollectionItemIdentityComparer();
            var objectComparerIterator = new ObjectComparerPropertyIterator(propertyIterator);

            return new ObjectComparer<Deal>(pathManager, objectComparerIterator)

                // идентифицирующие компараторы элементов коллекций
                .WithIdentityComparer(typeof(Driver), collectionItemIdentityComparer)
                .WithIdentityComparer(typeof(Period), collectionItemIdentityComparer)

                // сравнивающие компараторы
                .WithEqualityComparer(typeof(Driver), new DriverEqualityComparer())
                .WithEqualityComparer(typeof(Period), new PeriodEqualityComparer())
                .WithEqualityComparer(typeof(Auto), new AutoEqualityComparer())
                .WithEqualityComparer(typeof(AutoDeal), new AutoDealEqualityComparer())
                .WithEqualityComparer(typeof(Insurant), new InsurantEqualityComparer())
                .WithEqualityComparer(typeof(Owner), new OwnerEqualityComparer())

                // задание путей для ченджей
                .WithChangePath(DealPathConstants.Drivers)
                .WithChangePath(DealPathConstants.DealPeriods)
                .WithChangePath(DealPathConstants.Auto, DealPathConstants.AutoAltPath)
                .WithChangePath(DealPathConstants.AutoDeal)
                .WithChangePath(DealPathConstants.Insurant)
                .WithChangePath(DealPathConstants.Owner, DealPathConstants.OwnerAltPath);
        }

        private abstract class DefaultEqualityComparer : IEqualityComparer
        {
            public new abstract bool Equals(object x, object y);

            public int GetHashCode(object obj)
            {
                return obj.GetHashCode();
            }

            /// <summary>
            /// Сравение строк с учетом регистра
            /// Из ОСС текст может приходить капсом
            /// </summary>
            /// <param name="expected"></param>
            /// <param name="actual"></param>
            /// <returns></returns>
            protected bool StringCompare(string expected, string actual)
            {
                return string.Equals(expected, actual, StringComparison.OrdinalIgnoreCase);
            }
        }

        #region IdentityComparers
        private sealed class CollectionItemIdentityComparer : DefaultEqualityComparer
        {
            public override bool Equals(object x, object y)
            {
                var expected = (IUniqueble)x;
                var actual = (IUniqueble)y;

                return expected?.UniqueId == actual?.UniqueId;
            }
        }
        #endregion

        #region EqualityComparers

        private sealed class DriverEqualityComparer : DefaultEqualityComparer
        {
            public override bool Equals(object x, object y)
            {
                var expected = (Driver)x;
                var actual = (Driver)y;
                var expectedPerson = expected?.Subject?.IndividualEntity?.Person;
                var actualPerson = actual?.Subject?.IndividualEntity?.Person;
                var expectedDrivingExperience = expected?.DrivingExperiences.FirstOrDefault();
                var actualDrivingExperience = actual?.DrivingExperiences.FirstOrDefault();

                var isEqual =
                    StringCompare(expectedPerson?.LastName, actualPerson?.LastName)
                    && StringCompare(expectedPerson?.Name, actualPerson?.Name)
                    && StringCompare(expectedPerson?.MiddleName, actualPerson?.MiddleName)
                    && expectedPerson?.DateOfBirth == actualPerson?.DateOfBirth
                    && StringCompare(expected?.DriverLicenseSeries, actual?.DriverLicenseSeries)
                    && StringCompare(expected?.DriverLicenseNumber, actual?.DriverLicenseNumber)
                    && expectedDrivingExperience?.DrivingExperienceDateStart == actualDrivingExperience?.DrivingExperienceDateStart;

                return isEqual;
            }
        }

        private sealed class PeriodEqualityComparer : DefaultEqualityComparer
        {
            public override bool Equals(object x, object y)
            {
                var expected = (Period)x;
                var actual = (Period)y;
                var isEqual =
                       expected?.DateStart == actual?.DateStart
                       && expected?.DateEnd == actual?.DateEnd;

                return isEqual;
            }
        }

        private sealed class AutoEqualityComparer : DefaultEqualityComparer
        {
            public override bool Equals(object x, object y)
            {
                var expected = (Auto)x;
                var actual = (Auto)y;
                var expectedTsIdentityCard = expected?.TsIdentityCards?.FirstOrDefault();
                var actualTsIdentityCard = actual?.TsIdentityCards?.FirstOrDefault();

                return StringCompare(expected?.Vin, actual?.Vin)
                    && StringCompare(expected?.BodyNumber, actual?.BodyNumber)
                    && StringCompare(expected?.ChassisNumber, actual?.ChassisNumber)
                    && StringCompare(expected?.RegistrationNumber, actual?.RegistrationNumber)
                    && expected?.AutoYearOfIssueValue == actual?.AutoYearOfIssueValue
                    && StringCompare(expectedTsIdentityCard?.Series, actualTsIdentityCard?.Series)
                    && StringCompare(expectedTsIdentityCard?.Number, actualTsIdentityCard?.Number)
                    && expectedTsIdentityCard?.DateOfIssued == actualTsIdentityCard?.DateOfIssued
                    && StringCompare(expected?.DiagnosticCard?.Number, actual?.DiagnosticCard?.Number)
                    && expected?.DiagnosticCard?.DateDiagnostic == actual?.DiagnosticCard?.DateDiagnostic
                    && StringCompare(expected?.PurposeOfUsingCode, actual?.PurposeOfUsingCode)
                    && StringCompare(expected?.AutoTypeCode, actual?.AutoTypeCode)
                    && StringCompare(expected?.AutoYearOfIssueCode, actual?.AutoYearOfIssueCode)
                    && StringCompare(expected?.AutoEnginePowerCode, actual?.AutoEnginePowerCode)
                    && StringCompare(expected?.AutoEngineTypeCode, actual?.AutoEngineTypeCode)
                    && expected?.HasAutostart == actual?.HasAutostart
                    && expected?.OriginalKeysAmount == actual?.OriginalKeysAmount
                    && StringCompare(expected?.AutoTransmissionTypeCode, actual?.AutoTransmissionTypeCode)
                    && expected?.HasTrailer == actual?.HasTrailer
                    && expected?.AutoCost >= actual?.AutoCost
                    && expected?.HasAntiTheftSystem == actual?.HasAntiTheftSystem
                    && StringCompare(expected?.AutoBrandName, actual?.AutoBrandName)
                    && StringCompare(expected?.AutoModelName, actual?.AutoModelName);
            }
        }

        private sealed class AutoDealEqualityComparer : DefaultEqualityComparer
        {
            public override bool Equals(object x, object y)
            {
                var expected = (AutoDeal)x;
                var actual = (AutoDeal)y;
                var expectedTsIdentityCard = expected?.TsIdentityCard;
                var actualTsIdentityCard = actual?.TsIdentityCard;

                expected.DriverMinExperience = expected?.DriverMinExperience ?? 0;
                expected.DriverMinAge = expected?.DriverMinAge ?? 18;

                return expected?.IsMultidrive == actual?.IsMultidrive
                    && expected?.MonthsCount == actual?.MonthsCount
                    && StringCompare(expectedTsIdentityCard?.Series, actualTsIdentityCard?.Series)
                    && StringCompare(expectedTsIdentityCard?.Number, actualTsIdentityCard?.Number)
                    && expectedTsIdentityCard?.DateOfIssued == actualTsIdentityCard?.DateOfIssued
                    && StringCompare(expectedTsIdentityCard?.TsIdentityCardTypeCode, actualTsIdentityCard?.TsIdentityCardTypeCode)
                    && expected?.DriverMinAge == actual.DriverMinAge
                    && expected?.DriverMinExperience == actual.DriverMinExperience;
            }
        }

        private sealed class InsurantEqualityComparer : DefaultEqualityComparer
        {
            public override bool Equals(object x, object y)
            {
                var expected = (Insurant)x;
                var actual = (Insurant)y;
                var expectedPerson = expected?.Subject?.IndividualEntity?.Person;
                var actualPerson = actual?.Subject?.IndividualEntity?.Person;
                var expectedIdentityDocument = expected?.IdentityDocument;
                var actualIdentityDocument = actual?.IdentityDocument;

                return StringCompare(expectedPerson?.LastName, actualPerson?.LastName)
                    && StringCompare(expectedPerson?.Name, actualPerson?.Name)
                    && StringCompare(expectedPerson?.MiddleName, actualPerson?.MiddleName)
                    && StringCompare(expectedPerson?.SnilsNumber, actualPerson?.SnilsNumber)
                    && expectedPerson?.DateOfBirth == actualPerson?.DateOfBirth
                    && StringCompare(expectedIdentityDocument?.CitizenshipCode, actualIdentityDocument?.CitizenshipCode)
                    && StringCompare(expectedIdentityDocument?.IdentityDocumentTypeCode, actualIdentityDocument?.IdentityDocumentTypeCode)
                    && StringCompare(expectedIdentityDocument?.Series, actualIdentityDocument?.Series)
                    && StringCompare(expectedIdentityDocument?.Number, actualIdentityDocument?.Number)
                    && StringCompare(expectedIdentityDocument?.IssuedBy, actualIdentityDocument?.IssuedBy)
                    && expectedIdentityDocument?.DateIssue == actualIdentityDocument?.DateIssue
                    && StringCompare(expectedIdentityDocument?.UnitCode, actualIdentityDocument?.UnitCode)
                    && StringCompare(
                        expectedPerson?.Addresses?.FirstOrDefault(a => a.AddressTypeCode == AddressTypeCodes.Registration)?.EnteredAddress,
                        actualPerson?.Addresses?.FirstOrDefault(a => a.AddressTypeCode == AddressTypeCodes.Registration)?.EnteredAddress)
                    && StringCompare(
                        expectedPerson?.Addresses?.FirstOrDefault(a => a.AddressTypeCode == AddressTypeCodes.Fact)?.EnteredAddress,
                        actualPerson?.Addresses?.FirstOrDefault(a => a.AddressTypeCode == AddressTypeCodes.Fact)?.EnteredAddress)
                    && StringCompare(expectedPerson?.Phone1, actualPerson?.Phone1)
                    && StringCompare(expectedPerson.Email1, actualPerson?.Email1);
            }
        }

        private sealed class OwnerEqualityComparer : DefaultEqualityComparer
        {
            public override bool Equals(object x, object y)
            {
                var expected = (Owner)x;
                var actual = (Owner)y;
                var expectedPerson = expected?.Subject?.IndividualEntity?.Person;
                var actualPerson = actual?.Subject?.IndividualEntity?.Person;
                var expectedIdentityDocument = expected?.IdentityDocument;
                var actualIdentityDocument = actual?.IdentityDocument;

                return StringCompare(expectedPerson?.LastName, actualPerson?.LastName)
                    && StringCompare(expectedPerson?.Name, actualPerson?.Name)
                    && StringCompare(expectedPerson?.MiddleName, actualPerson?.MiddleName)
                    && StringCompare(expectedPerson?.SnilsNumber, actualPerson?.SnilsNumber)
                    && expectedPerson?.DateOfBirth == actualPerson?.DateOfBirth
                    && StringCompare(expectedIdentityDocument?.CitizenshipCode, actualIdentityDocument?.CitizenshipCode)
                    && StringCompare(expectedIdentityDocument?.IdentityDocumentTypeCode, actualIdentityDocument?.IdentityDocumentTypeCode)
                    && StringCompare(expectedIdentityDocument?.Series, actualIdentityDocument?.Series)
                    && StringCompare(expectedIdentityDocument?.Number, actualIdentityDocument?.Number)
                    && StringCompare(expectedIdentityDocument?.IssuedBy, actualIdentityDocument?.IssuedBy)
                    && expectedIdentityDocument?.DateIssue == actualIdentityDocument?.DateIssue
                    && StringCompare(expectedIdentityDocument?.UnitCode, actualIdentityDocument?.UnitCode)
                    && StringCompare(
                        expectedPerson?.Addresses?.FirstOrDefault(a => a.AddressTypeCode == AddressTypeCodes.Registration)?.EnteredAddress,
                        actualPerson?.Addresses?.FirstOrDefault(a => a.AddressTypeCode == AddressTypeCodes.Registration)?.EnteredAddress)
                    && StringCompare(
                        expectedPerson?.Addresses?.FirstOrDefault(a => a.AddressTypeCode == AddressTypeCodes.Fact)?.EnteredAddress,
                        actualPerson?.Addresses?.FirstOrDefault(a => a.AddressTypeCode == AddressTypeCodes.Fact)?.EnteredAddress)
                    && StringCompare(expectedPerson?.RegistrationRegionCode, actualPerson?.RegistrationRegionCode);
            }
        }
        #endregion
    }
}

using System.Collections.Generic;
using System.Linq;
using iRIS.Framework.Entity.Metadata.Manager.Interfaces;
using iRIS.InsuranceApp.Infrastructure.Extensions;
using iRIS.InsuranceCore.Code.Implementations.Comparison;
using iRIS.InsuranceCore.Code.Implementations.Comparison.DisplayedChanges;
using iRIS.InsuranceCore.Entities;
using iRIS.InsuranceCore.Entities.Custom;
using iRIS.InsuranceCore.Entities.EntityExtensions;
using iRIS.InsuranceCore.Infrastructure.Constants;
using iRIS.InsuranceCore.Infrastructure.Helpers;
using iRIS.KaskoLink2.Modification.Code.Constants;

namespace iRIS.KaskoLink2.Modification.Code.Implementations.Comparison
{
    public class KaskoDisplayedChangesFormatter : DisplayedChangesFormatter
    {
        public KaskoDisplayedChangesFormatter(IPathManager pathManager, Dictionary<string, List<ObjectCompareResult>> changes)
            : base(pathManager, 30)
        {
            // Periods
            AddRule(
                period => FormatPeriod(period as Period),
                "Period.DateStart",
                "Period.DateEnd");

            // Insurant
            AddRuleWithInfo(
                "ФИО страхователя",
                "Данные были изменены в профиле личного кабинета",
                insurant => FormatPersonName((insurant as Insurant)?.Subject),
                "Insurant.Subject.IndividualEntity.Person.LastName",
                "Insurant.Subject.IndividualEntity.Person.Name",
                "Insurant.Subject.IndividualEntity.Person.MiddleName");
            AddRuleWithInfo(
                "Дата рождения страхователя",
                "Данные были изменены в профиле личного кабинета",
                "Insurant.Subject.IndividualEntity.Person.DateOfBirth");
            AddRuleWithInfo(
                "Гражданство",
                "Данные были изменены в профиле личного кабинета",
                insurant => FormatCitizenship((insurant as Insurant)?.IdentityDocument?.Citizenship),
                "Insurant.IdentityDocument.CitizenshipCode",
                "Insurant.IdentityDocument.Citizenship.Code");
            AddRuleWithInfo(
                "Паспортные данные страхователя",
                "Данные были изменены в профиле личного кабинета",
                insurant => FormatPersonIdentityDocument((insurant as Insurant)?.IdentityDocument),
                "Insurant.IdentityDocument.IdentityDocumentTypeCode",
                "Insurant.IdentityDocument.IdentityDocumentType.Code",
                "Insurant.IdentityDocument.Series",
                "Insurant.IdentityDocument.Number",
                "Insurant.IdentityDocument.UnitCode",
                "Insurant.IdentityDocument.DateIssue",
                "Insurant.IdentityDocument.IssuedBy");
            AddRuleWithCustomCaption(
                insurant => GetAddressCaption((insurant as Insurant).GetPerson().Addresses[0], nameof(Insurant)),
                insurant => (insurant as Insurant).GetPerson()?.Addresses[0]?.EnteredAddress,
                "Insurant.Subject.IndividualEntity.Person.Addresses[0].EnteredAddress");
            AddRuleWithCustomCaption(
                insurant => GetAddressCaption((insurant as Insurant).GetPerson().Addresses[1], nameof(Insurant)),
                insurant => (insurant as Insurant).GetPerson()?.Addresses[1]?.EnteredAddress,
                "Insurant.Subject.IndividualEntity.Person.Addresses[1].EnteredAddress");
            AddRule(
                "Email страхователя",
                "Insurant.Subject.IndividualEntity.Person.Email1");
            AddRule(
                "Телефон страхователя",
                "Insurant.Subject.IndividualEntity.Person.Phone1");
            AddRule(
                "СНИЛС страхователя",
                "Insurant.Subject.IndividualEntity.Person.SnilsNumber");

            // Owner
            AddRule(
                "ФИО собственника",
                owner => FormatPersonName((owner as Owner)?.Subject),
                "Owner.Subject.IndividualEntity.Person.LastName",
                "Owner.Subject.IndividualEntity.Person.Name",
                "Owner.Subject.IndividualEntity.Person.MiddleName");
            AddRule(
                "Дата рождения собственника",
                "Owner.Subject.IndividualEntity.Person.DateOfBirth");
            AddRule(
                "Гражданство",
                owner => FormatCitizenship((owner as Owner)?.IdentityDocument?.Citizenship),
                "Owner.IdentityDocument.CitizenshipCode",
                "Owner.IdentityDocument.Citizenship.Code");
            AddRule(
                "Паспортные данные собственника",
                owner => FormatPersonIdentityDocument((owner as Owner)?.IdentityDocument),
                "Owner.IdentityDocument.IdentityDocumentTypeCode",
                "Owner.IdentityDocument.IdentityDocumentType.Code",
                "Owner.IdentityDocument.Series",
                "Owner.IdentityDocument.Number",
                "Owner.IdentityDocument.UnitCode",
                "Owner.IdentityDocument.DateIssue",
                "Owner.IdentityDocument.IssuedBy");
            AddRuleWithCustomCaption(
                owner => GetAddressCaption((owner as Owner).GetPerson().Addresses[0], nameof(Owner)),
                owner => (owner as Owner).GetPerson()?.Addresses[0]?.EnteredAddress,
                "Owner.Subject.IndividualEntity.Person.Addresses[0].EnteredAddress");
            AddRuleWithCustomCaption(
                owner => GetAddressCaption((owner as Owner).GetPerson().Addresses[1], nameof(Owner)),
                owner => (owner as Owner).GetPerson()?.Addresses[1]?.EnteredAddress,
                "Owner.Subject.IndividualEntity.Person.Addresses[1].EnteredAddress");
            AddRule(
                "СНИЛС собственника",
                "Owner.Subject.IndividualEntity.Person.SnilsNumber");
            AddRule(
                "Email собственника",
                "Owner.Subject.IndividualEntity.Person.Email1");

            // Auto
            changes.TryGetValue("InsuranceObjects[0].Auto", out var autoChanges);
            var actualAuto = autoChanges?.FirstOrDefault()?.ActualObject as Auto;
            var expectedAuto = autoChanges?.FirstOrDefault()?.ExpectedObject as Auto;

            AddRule("Год выпуска ТС", "Auto.AutoYearOfIssueCode");
            AddRule("VIN", "Auto.Vin");
            AddRule("Шасси (рама)", "Auto.ChassisNumber");
            AddRule("Кузов (прицеп)", "Auto.BodyNumber");
            AddRule("Смена рег. знака", "Auto.RegistrationNumber");
            AddRule("Мощность двигателя", "Auto.AutoEnginePowerCode");
            AddRule("Коробка передач", "Auto.AutoTransmissionTypeCode");
            AddRule("Тип двигателя ТС", "Auto.AutoEngineTypeCode");
            AddRule("Установка системы автоматического запуска двигателя", "Auto.HasAutostart");
            AddRule("Прицеп", auto => FormatAutoHasTrailer(auto as Auto), "Auto.HasTrailer");
            AddRule("Цель использования", auto => (auto as Auto)?.PurposeOfUsing?.Name, "Auto.PurposeOfUsingCode", "Auto.PurposeOfUsing.Code");
            AddRuleWithCustomCaption(
                auto => (auto as Auto)?.TsIdentityCards?.FirstOrDefault()?.TsIdentityCardType?.Name,
                auto => FormatAutoTsIdentityCard(auto as Auto),
                "Auto.TsIdentityCards[0].TsIdentityCardTypeCode",
                "Auto.TsIdentityCards[0].TsIdentityCardType.Code",
                "Auto.TsIdentityCards[0].Series",
                "Auto.TsIdentityCards[0].Number",
                "Auto.TsIdentityCards[0].DateOfIssued");
            AddRule("Номер диагностической карты", "Auto.DiagnosticCard.Number");
            AddRule("Дата окончания диагностической карты", "Auto.DiagnosticCard.DateEnd");
            AddRule("Увеличение страховой суммы по рискам \"Ущерб\" и \"Угон / Хищение\"", "Auto.AutoCost");
            AddRule("Демонтаж ПСС и / или ПУУ", "Auto.HasAntiTheftSystem");
            AddRule("Марка и / или Модель", "Auto.AutoBrandName");
            AddRule("Марка и / или Модель", "Auto.AutoModelName");

            if (actualAuto?.OriginalKeysAmount == 0 &&
                (expectedAuto?.OriginalKeysAmount > 0 ||
                expectedAuto?.OriginalKeysAmount == null))
            {
                AddRule("Кража ключей", "Auto.OriginalKeysAmount");
            }

            if (actualAuto?.OriginalKeysAmount >= 2 &&
                (expectedAuto?.OriginalKeysAmount < 2 ||
                expectedAuto?.OriginalKeysAmount == null))
            {
                AddRule("Количество ключей меняется на 2 или более", "Auto.OriginalKeysAmount");
            }

            if (actualAuto?.OriginalKeysAmount == 1 &&
                (expectedAuto?.OriginalKeysAmount == 2 ||
                expectedAuto?.OriginalKeysAmount == null))
            {
                AddRule("Количество ключей меняется с 2 - х на 1", "Auto.OriginalKeysAmount");
            }

            // Drivers
            AddRule(
                "ФИО водителя",
                driver => FormatDriverName(driver as Driver),
                "Driver.Subject.IndividualEntity.Person.LastName",
                "Driver.Subject.IndividualEntity.Person.Name",
                "Driver.Subject.IndividualEntity.Person.MiddleName");
            AddRule(
                null,
                driver => FormatDriverDateOfBirth(driver as Driver),
                "Driver.Subject.IndividualEntity.Person.DateOfBirth");
            AddRule(
                null,
                driver => FormatDriverLicense(driver as Driver),
                "Driver.DriverLicenseSeries",
                "Driver.DriverLicenseNumber");
            AddRule(
                null,
                driver => FormatIsForeingDriverLicense(driver as Driver),
                "Driver.IsForeignDrivingLicense");
            AddRule(
                null,
                driver => FormatDriverDrivingExperiences(driver as Driver),
                "Driver.DrivingExperiences[0].DrivingExperienceDateStart");

            // AutoDeal
            AddRule(
                "Изменен тип использования",
                autoDeal => FormatPeriodType(autoDeal as AutoDeal),
                "AutoDeal.InsurancePeriodTypeCode");
            AddRule(
                "Изменен тип использования",
                autoDeal => FormatIsMultidrive(autoDeal as AutoDeal),
                "AutoDeal.IsMultidrive");
            AddRule(
                "ПТС",
                autoDeal => FormatAutoTsIdentityCard(autoDeal as AutoDeal),
                "AutoDeal.TsIdentityCard.TsIdentityCardTypeCode",
                "AutoDeal.TsIdentityCard.TsIdentityCardType.Code",
                "AutoDeal.TsIdentityCard.Series",
                "AutoDeal.TsIdentityCard.Number",
                "AutoDeal.TsIdentityCard.DateOfIssued");

            AddRule("Изменение возраста / стажа", "AutoDeal.DriverMinAge");
            AddRule("Изменение возраста / стажа", "AutoDeal.DriverMinExperience");

            // TransformRules
            AddTransformRule(new DisplayedChangesFormatterTransformRule
            {
                ChangedField = "AutoDeal.InsurancePeriodTypeCode",
                SourcePath = DealPathConstants.AutoDeal,
                DestinationPath = DealPathConstants.DealPeriods,
                ItemsShouldBeRemovedFunc = autoDeal => (autoDeal as AutoDeal)?.InsurancePeriodTypeCode == InsurancePeriodTypeCodes.OneYear,
                ItemsShouldBeIsAddedFunc = autoDeal => (autoDeal as AutoDeal)?.InsurancePeriodTypeCode == InsurancePeriodTypeCodes.UsePeriod
            });
            AddTransformRule(new DisplayedChangesFormatterTransformRule
            {
                ChangedField = "AutoDeal.IsMultidrive",
                SourcePath = DealPathConstants.AutoDeal,
                DestinationPath = DealPathConstants.Drivers,
                ItemsShouldBeRemovedFunc = autoDeal => (autoDeal as AutoDeal)?.IsMultidrive == true,
                ItemsShouldBeIsAddedFunc = autoDeal => (autoDeal as AutoDeal)?.IsMultidrive == false
            });
        }

        private string GetAddressCaption(Address address, string typeName) =>
            address.AddressTypeCode switch
            {
                AddressTypeCodes.Registration when typeName == nameof(Insurant) => ChangesCaptions.InsurantRegistrationAddress,
                AddressTypeCodes.Registration when typeName == nameof(Owner) => ChangesCaptions.OwnerRegistrationAddress,
                AddressTypeCodes.Fact when typeName == nameof(Insurant) => ChangesCaptions.InsurantFactAddress,
                AddressTypeCodes.Fact when typeName == nameof(Owner) => ChangesCaptions.OwnerFactAddress,
                _ => string.Empty,
            };

        protected override int GetObjectOrderNumber(string path)
        {
            switch (path)
            {
                case DealPathConstants.DealPeriods:
                    return 1;

                case DealPathConstants.Insurant:
                    return 2;

                case DealPathConstants.Owner:
                    return 3;

                case DealPathConstants.Auto:
                    return 4;

                case DealPathConstants.Drivers:
                    return 5;

                default:
                    return int.MaxValue;
            }
        }

        protected override string GetObjectTitle(string path)
        {
            switch (path)
            {
                case DealPathConstants.DealPeriods:
                    return "Периоды использования";

                case DealPathConstants.Insurant:
                    return "Страхователь";

                case DealPathConstants.Owner:
                    return "Владелец ТС";

                case DealPathConstants.Auto:
                    return "Транспортное средство";

                case DealPathConstants.Drivers:
                    return "Изменение списка ЛДУ";
            }

            return null;
        }

        protected override string GetItemName(string path)
        {
            if (path == DealPathConstants.Drivers)
            {
                return "водитель";
            }

            if (path == DealPathConstants.DealPeriods)
            {
                return "период";
            }

            return null;
        }

        protected override string GetItemText(string path, ObjectCompareResult compareResult)
        {
            if (path == DealPathConstants.DealPeriods)
            {
                if (compareResult.IsAdded)
                {
                    return FormatPeriod(compareResult.ActualObject as Period);
                }

                if (compareResult.IsDeleted)
                {
                    return FormatPeriod(compareResult.ExpectedObject as Period);
                }

                return null;
            }

            if (path == DealPathConstants.Drivers)
            {
                if (compareResult.IsAdded)
                {
                    return FormatDriver(compareResult.ActualObject as Driver);
                }

                if (compareResult.IsDeleted)
                {
                    return FormatDriverName(compareResult.ExpectedObject as Driver);
                }

                if (!compareResult.ChangedFields.Any(x => x.EndsWith("Name")))
                {
                    return FormatDriverName(compareResult.ExpectedObject as Driver);
                }
            }

            return null;
        }

        private static string FormatPeriodType(AutoDeal autoDeal)
        {
            if (autoDeal == null)
            {
                return null;
            }

            return autoDeal.InsurancePeriodTypeCode == InsurancePeriodTypeCodes.OneYear
                ? "Годовое использование"
                : "Периоды использования";
        }

        private static string FormatPeriod(Period period)
        {
            if (period == null)
            {
                return null;
            }

            var monthDifference = DurationHelper.FormatMonthDifference(
                period.DateStart.GetValueOrDefault(),
                period.DateEnd.GetValueOrDefault());

            return $"{monthDifference}, {period.DateStart?.ToShortDateForUi()} - {period.DateEnd?.ToShortDateForUi()}";
        }

        private static string FormatPersonName(Subject subject)
        {
            var person = subject?.IndividualEntity?.Person;

            if (person == null)
            {
                return null;
            }

            return $"{person.LastName} {person.Name} {person.MiddleName}".TrimEnd();
        }

        private static string FormatCitizenship(Citizenship citizenship)
        {
            if (citizenship == null)
            {
                return null;
            }

            if (citizenship.Code == "other")
            {
                return "Иное";
            }

            return citizenship.FullName;
        }

        private static string FormatPersonIdentityDocument(IdentityDocument document)
        {
            if (document == null)
            {
                return null;
            }

            var unitCodeString = string.IsNullOrEmpty(document.UnitCode)
                ? string.Empty
                : $", код подразделения {document.UnitCode}";

            var issueInfoString = document.DateIssue.HasValue || !string.IsNullOrEmpty(document.IssuedBy)
                ? $", {document.DateIssue?.ToShortDateForUi()} {document.IssuedBy}"
                : string.Empty;

            return $"{document.IdentityDocumentType?.Name} {document.Series} {document.Number}{unitCodeString}{issueInfoString}"
                .Trim();
        }

        private static string FormatAutoHasTrailer(Auto auto)
        {
            return auto?.HasTrailer == true ? "С прицепом" : "Без прицепа";
        }

        private static string FormatAutoTsIdentityCard(Auto auto)
        {
            var tsIdentityCard = auto?.TsIdentityCards?.FirstOrDefault();
            if (tsIdentityCard == null)
            {
                return null;
            }

            var dateOfIssuedString = tsIdentityCard.DateOfIssued.HasValue
                ? $", дата выдачи {tsIdentityCard.DateOfIssued.Value.ToShortDateForUi()}"
                : string.Empty;

            return $"{tsIdentityCard.Series} {tsIdentityCard.Number}{dateOfIssuedString}";
        }

        private static string FormatAutoTsIdentityCard(AutoDeal autoDeal)
        {
            var tsIdentityCard = autoDeal?.TsIdentityCard;
            if (tsIdentityCard == null)
            {
                return null;
            }

            var dateOfIssuedString = tsIdentityCard.DateOfIssued.HasValue
                ? $", дата выдачи {tsIdentityCard.DateOfIssued.Value.ToShortDateForUi()}"
                : string.Empty;

            return $"{tsIdentityCard.Series} {tsIdentityCard.Number}{dateOfIssuedString}";
        }

        private static string FormatIsMultidrive(AutoDeal autoDeal)
        {
            if (autoDeal == null)
            {
                return null;
            }

            return autoDeal.IsMultidrive == true ? "Без ограничения" : "Ограниченный круг водителей";
        }

        public static string FormatDriver(Driver driver)
        {
            if (driver == null)
            {
                return null;
            }

            var fields = new List<string>(4)
            {
                FormatDriverName(driver),
                FormatDriverDateOfBirth(driver),
                FormatDriverLicense(driver),
                FormatDriverDrivingExperiences(driver)
            };

            return string.Join(", ", fields.Where(x => x != null));
        }

        private static string FormatDriverName(Driver driver)
        {
            if (driver == null)
            {
                return null;
            }

            return FormatPersonName(driver.Subject);
        }

        private static string FormatDriverDateOfBirth(Driver driver)
        {
            var dateOfBirth = driver?.Subject?.IndividualEntity?.Person?.DateOfBirth;

            return dateOfBirth.HasValue
                ? $"дата рождения {dateOfBirth.Value.ToShortDateForUi()}"
                : null;
        }

        private static string FormatDriverLicense(Driver driver)
        {
            if (driver == null || (string.IsNullOrEmpty(driver.DriverLicenseSeries) && string.IsNullOrEmpty(driver.DriverLicenseNumber)))
            {
                return null;
            }

            return $"ВУ №{driver.DriverLicenseSeries} {driver.DriverLicenseNumber}";
        }

        private static string FormatIsForeingDriverLicense(Driver driver)
        {
            if (driver?.IsForeignDrivingLicense == null)
            {
                return null;
            }

            var result = "Иностранное ВУ";

            return driver.IsForeignDrivingLicense == true
                ? $"{result} Да"
                : $"{result} Нет";
        }

        private static string FormatDriverDrivingExperiences(Driver driver)
        {
            return driver?.DrivingExperiences.Count > 0
                ? $"стаж вождения с {driver.DrivingExperiences[0].DrivingExperienceDateStart?.ToShortDateForUi()}"
                : null;
        }
    }
}