using System;
using System.Collections.Generic;
using System.Linq;
using iRIS.Framework.Data;
using iRIS.InsuranceCore.Code.Implementations.Comparison;
using iRIS.InsuranceCore.Entities;
using iRIS.InsuranceCore.Entities.Custom;
using iRIS.KaskoLink2.Modification.Code.Constants;

namespace iRIS.KaskoLink2.Modification.Code.Implementations.ChangeValidations
{
    /// <summary>
    /// Валидатор допустимых для ВИ Link2 отличий.
    /// </summary>
    public class KaskoModificationChangesLink2Validator : BaseChangesValidator
    {
        private const int AllowedLevenshteinDistance = 2;
        private const int YearOfIssueChangeLimit = 1;
        private const int EnginePowerChangePercentLimit = 20;

        private readonly string[] _driversFieldsAllowedForChanges = { ChangesPaths.DriverName, ChangesPaths.DriverMiddleName, ChangesPaths.DriverLastName };
        private readonly string[] _insurantFieldsAllowedForChanges = { ChangesPaths.InsurantName, ChangesPaths.InsurantMiddleName, ChangesPaths.InsurantLastName };
        private readonly string[] _ownerFieldsAllowedForChanges = { ChangesPaths.OwnerName, ChangesPaths.OwnerMiddleName, ChangesPaths.OwnerLastName };
        private readonly string[] _autoNumberFields = { ChangesPaths.AutoVin };
        private readonly string[] _autoDocumentFields = { ChangesPaths.AutoDocNumber, ChangesPaths.AutoDocSeries, ChangesPaths.AutoDocDateOfIssued };

        /// <summary>
        /// Initializes a new instance of the <see cref="KaskoModificationChangesLink2Validator"/> class.
        /// </summary>
        /// <param name="changes">Словарь с результатами сравнения корзин.</param>
        public KaskoModificationChangesLink2Validator(Dictionary<string, List<ObjectCompareResult>> changes)
            : base(changes)
        {
        }

        public ChangeValidationResult CheckAutoDeal()
        {
            var resultErrors = new List<string>();

            var autoDealChanges = GetChangedItems(DealPathConstants.AutoDeal);
            if (autoDealChanges.Count == 0 && resultErrors.Count == 0)
            {
                return ChangeValidationResult.Empty();
            }

            if (!CheckIsMultidriveChanged(autoDealChanges))
            {
                resultErrors.Add("Не допускается изменение признака мультидрайва.");
            }

            return new ChangeValidationResult(resultErrors);
        }

        private bool CheckIsMultidriveChanged(List<ObjectCompareResult> autoDealChanges) => !autoDealChanges.Any(dr => dr.ChangedFields.Any(f => f == ChangesPaths.AutoDealIsMultidrive));

        /// <summary>
        /// Выполняет проверку допустимости изменений данных страхователя.
        /// </summary>
        /// <returns>Результат проверки.</returns>
        public ChangeValidationResult CheckInsurant(bool? isInsurantGettingMarried)
        {
            var resultErrors = new List<string>();

            var insurantChanges = GetChangedItems(DealPathConstants.Insurant);
            if (insurantChanges.Count == 0)
            {
                return ChangeValidationResult.Empty();
            }

            if (HasInsurantChangedCompletely(insurantChanges))
            {
                return ChangeValidationResult.Empty();
            }

            if (!CheckLevenshteinDistanceForInsurantFullNameChanges(insurantChanges, isInsurantGettingMarried))
            {
                var errorMessage = isInsurantGettingMarried == true
                    ? "Не допускается изменение более " + AllowedLevenshteinDistance + " символов в поле имя и отчество страхователя."
                    : "Не допускается изменение более " + AllowedLevenshteinDistance + " символов в поле ФИО страхователя.";
                resultErrors.Add(errorMessage);
            }

            return new ChangeValidationResult(resultErrors);
        }

        /// <summary>
        /// Проверяем полное изменение страхователя или частичное.
        /// </summary>
        /// <param name="insurantChanges"></param>
        /// <returns></returns>
        private bool HasInsurantChangedCompletely(List<ObjectCompareResult> insurantChanges)
        {
            var fullNameChanges = insurantChanges.Any(change =>
                change.ChangedFields.Any(field => field == ChangesPaths.InsurantLastName || field == ChangesPaths.InsurantName || field == ChangesPaths.InsurantMiddleName));
            var dateOfBirthChange = insurantChanges.Any(change => change.ChangedFields.Any(field => field == ChangesPaths.InsurantDateOfBirth));

            var identityDocumentChanges = insurantChanges.Any(change => change.ChangedFields.Any(field => field == ChangesPaths.InsurantIdentityDocumentSeries || field == ChangesPaths.InsurantIdentityDocumentNumber));
            return fullNameChanges && dateOfBirthChange && identityDocumentChanges;
        }

        /// <summary>
        /// Проверяем полное изменение собственника или частичное.
        /// </summary>
        /// <param name="insurantChanges"></param>
        /// <returns></returns>
        private bool HasOwnerChangedCompletely(List<ObjectCompareResult> ownerChanges)
        {
            var fullNameChanges = ownerChanges.Any(change =>
                change.ChangedFields.Any(field => field == ChangesPaths.OwnerLastName || field == ChangesPaths.OwnerName || field == ChangesPaths.OwnerMiddleName));
            var dateOfBirthChange = ownerChanges.Any(change => change.ChangedFields.Any(field => field == ChangesPaths.OwnerDateOfBirth));

            var identityDocumentChanges = ownerChanges.Any(change => change.ChangedFields.Any(field => field == ChangesPaths.OwnerIdentityDocumentSeries || field == ChangesPaths.OwnerIdentityDocumentNumber));
            return fullNameChanges && dateOfBirthChange && identityDocumentChanges;
        }

        /// <summary>
        /// Выполняет проверку допустимости изменений данных собственника.
        /// </summary>
        /// <returns>Результат проверки.</returns>
        public ChangeValidationResult CheckOwner(bool? isOwnerGettingMerried)
        {
            var resultErrors = new List<string>();

            var ownerChanges = GetChangedItems(DealPathConstants.Owner);
            if (ownerChanges.Count == 0)
            {
                return ChangeValidationResult.Empty();
            }

            if (HasOwnerChangedCompletely(ownerChanges))
            {
                return ChangeValidationResult.Empty();
            }

            if (!CheckLevenshteinDistanceForOwnerFullNameChanges(ownerChanges, isOwnerGettingMerried))
            {
                var errorMessage = isOwnerGettingMerried == true
                    ? "Не допускается изменение более " + AllowedLevenshteinDistance + " символов в поле имя и отчество собственника."
                    : "Не допускается изменение более " + AllowedLevenshteinDistance + " символов в поле ФИО собственника.";
                resultErrors.Add(errorMessage);
            }

            return new ChangeValidationResult(resultErrors);
        }

        /// <summary>
        /// Выполняет проверку допустимости изменений данных водителей.
        /// </summary>
        /// <returns>Результат проверки.</returns>
        public ChangeValidationResult CheckDrivers(bool? isDriverGettingMerried)
        {
            var resultErrors = new List<string>();

            var driverChanges = GetChangedItems(DealPathConstants.Drivers);
            if (driverChanges.Count == 0)
            {
                return ChangeValidationResult.Empty();
            }

            if (!CheckLevenshteinDistanceForDriverFullNameChanges(driverChanges, isDriverGettingMerried))
            {
                var errorMessage = isDriverGettingMerried == true
                    ? "Не допускается изменение более " + AllowedLevenshteinDistance + " символов в поле имя и отчество водителя."
                    : "Не допускается изменение более " + AllowedLevenshteinDistance + " символов в поле ФИО водителя.";
                resultErrors.Add(errorMessage);
            }

            return new ChangeValidationResult(resultErrors);
        }

        /// <summary>
        /// Выполняет проверку допустимости изменений данных авто.
        /// </summary>
        /// <param name="isPts">Признак документа ПТС.</param>
        /// <returns>Результат проверки.</returns>
        public ChangeValidationResult CheckAuto()
        {
            var resultErrors = new List<string>();

            var autoChanges = GetChangedItems(DealPathConstants.Auto);

            if (autoChanges.Count == 0)
            {
                return ChangeValidationResult.Empty();
            }

            if (!CheckAutoLevenshteinDistanceForNumbersChanges(autoChanges))
            {
                resultErrors.Add($"Не допускается изменение более {AllowedLevenshteinDistance} символов в VIN.");
            }

            var autoDealChanges = GetChangedItems(DealPathConstants.AutoDeal);
            var isAutoNumberChanged = autoChanges.Any(a => a.ChangedFields.Any(f => _autoNumberFields.Contains(f)));            
            var isAutoDocumentChanged = autoDealChanges.Any(a => a.ChangedFields.Any(f => _autoDocumentFields.Contains(f)));
            
            if (isAutoDocumentChanged && isAutoNumberChanged)
            {
                resultErrors.Add("Не допускается одновременное изменение VIN  и ПТС.");
            }

            if (!CheckAutoYearOfIssueChanges(autoChanges))
            {
                resultErrors.Add($"Не допускается изменение года выпуска ТС более чем на 1 год.");
            }

            if (!CheckAutoEnginePowerCodeChanges(autoChanges))
            {
                resultErrors.Add($"Не допускается изменение мощности ТС более чем на 20%.");
            }

            if (!CheckHasAutostartChangesAndKeysAmount(autoChanges))
            {
                resultErrors.Add($"Не допускается изменение признака автозапуска и количества ключей.");
            }

            if (!CheckAntiTheftSystemChanges(autoChanges))
            {
                resultErrors.Add($"Возможно только изменение признака демонтажа ПСС и / или ПУУ.");
            }

            return new ChangeValidationResult(resultErrors);
        }

        private bool CheckAntiTheftSystemChanges(List<ObjectCompareResult> autoChanges)
        {
            var fieldValues = new Dictionary<string, Func<Auto, bool>>
            {
                [ChangesPaths.HasAntiTheftSystem] = i => i.HasAntiTheftSystem ?? false,
            };

            return !autoChanges.Any(change =>
            {
                var exp = (Auto)change.ExpectedObject;
                var act = (Auto)change.ActualObject;

                return fieldValues.Any(
                    fieldValue =>
                        change.ChangedFields.Contains(fieldValue.Key)
                        && !fieldValue.Value(exp)
                        && fieldValue.Value(act));
            });
        }

        private bool CheckHasAutostartChangesAndKeysAmount(List<ObjectCompareResult> autoChanges)
        {
            return !autoChanges.Any(dr => dr.ChangedFields.Any(f => f == ChangesPaths.AutoHasAutostart) 
                && dr.ChangedFields.Any(f => f == ChangesPaths.AutoOriginalKeysAmount));
        }

        private bool CheckAutoEnginePowerCodeChanges(List<ObjectCompareResult> autoChanges)
        {
            var fieldValues = new Dictionary<string, Func<Auto, string>>
            {
                [ChangesPaths.AutoEnginePowerCode] = i => i.AutoEnginePowerCode,
            };

            return !autoChanges.Any(change =>
            {
                var exp = (Auto)change.ExpectedObject;
                var act = (Auto)change.ActualObject;

                return fieldValues.Any(
                    fieldValue =>
                        change.ChangedFields.Contains(fieldValue.Key)
                        && GetPercentDifference(fieldValue.Value(exp), fieldValue.Value(act)) > EnginePowerChangePercentLimit);
            });
        }

        private double GetPercentDifference(string v1, string v2)
        {
            var x = double.Parse(v1);
            var y = double.Parse(v2);

            return Math.Abs((100 * (y - x)) / x);
        }

        private bool CheckAutoYearOfIssueChanges(List<ObjectCompareResult> autoChanges)
        {
            var fieldValues = new Dictionary<string, Func<Auto, string>>
            {
                [ChangesPaths.AutoYearOfIssueCode] = i => i.AutoYearOfIssueCode,
            };

            return !autoChanges.Any(change =>
            {
                var exp = (Auto)change.ExpectedObject;
                var act = (Auto)change.ActualObject;

                return fieldValues.Any(
                    fieldValue =>
                        change.ChangedFields.Contains(fieldValue.Key)
                        && GetYearDifference(fieldValue.Value(exp), fieldValue.Value(act)) > YearOfIssueChangeLimit);
            });
        }

        private int GetYearDifference(string v1, string v2)
        {
            int x = int.Parse(v1);
            int y = int.Parse(v2);

            return Math.Abs(x - y);
        }

        private bool CheckLevenshteinDistanceForDriverFullNameChanges(List<ObjectCompareResult> driversChanges, bool? isDriverGettingMerried)
        {
            var allowedDrivesChanges = driversChanges
                .Where(dr => dr.ChangedFields.Any(f => _driversFieldsAllowedForChanges.Contains(f))).ToList();

            var fieldsValues = new Dictionary<string, Func<Driver, string>>
            {
                [ChangesPaths.DriverName] = d => d.Subject.IndividualEntity.Person.Name,
                [ChangesPaths.DriverMiddleName] = d => d.Subject.IndividualEntity.Person.MiddleName,
            };

            if (isDriverGettingMerried != true)
            {
                fieldsValues[ChangesPaths.DriverLastName] = d => d.Subject.IndividualEntity.Person.LastName;
            }

            var checkLevenshtein = CheckLevenshteinDistanceFor(
                allowedDrivesChanges,
                fieldsValues);
            return checkLevenshtein;
        }

        private bool CheckLevenshteinDistanceForInsurantFullNameChanges(List<ObjectCompareResult> insurantChanges, bool? isInsurantGettingMarried)
        {
            var allowedInsurantChanges = insurantChanges
                .Where(dr => dr.ChangedFields.Any(f => _insurantFieldsAllowedForChanges.Contains(f))).ToList();

            var fieldsValues = new Dictionary<string, Func<Insurant, string>>
            {
                [ChangesPaths.InsurantName] = i => i.Subject.IndividualEntity.Person.Name,
                [ChangesPaths.InsurantMiddleName] = i => i.Subject.IndividualEntity.Person.MiddleName
            };

            if (isInsurantGettingMarried != true)
            {
                fieldsValues[ChangesPaths.InsurantLastName] = i => i.Subject.IndividualEntity.Person.LastName;
            }

            var checkLevenshtein = CheckLevenshteinDistanceFor(
                allowedInsurantChanges,
                fieldsValues);

            return checkLevenshtein;
        }

        private bool CheckLevenshteinDistanceForOwnerFullNameChanges(List<ObjectCompareResult> ownerChanges, bool? isOwnerGettingMerried)
        {
            var allowedOwnerChanges = ownerChanges
                .Where(dr => dr.ChangedFields.Any(f => _ownerFieldsAllowedForChanges.Contains(f))).ToList();

            var fieldsValues = new Dictionary<string, Func<Owner, string>>
            {
                [ChangesPaths.OwnerName] = i => i.Subject.IndividualEntity.Person.Name,
                [ChangesPaths.OwnerMiddleName] = i => i.Subject.IndividualEntity.Person.MiddleName,
            };

            if (isOwnerGettingMerried != true)
            {
                fieldsValues[ChangesPaths.OwnerLastName] = i => i.Subject.IndividualEntity.Person.LastName;
            }

            var checkLevenshtein = CheckLevenshteinDistanceFor(
                allowedOwnerChanges,
                fieldsValues);
            return checkLevenshtein;
        }

        private bool CheckAutoLevenshteinDistanceForNumbersChanges(List<ObjectCompareResult> autoChanges)
        {
            var allowedAutoChanges = autoChanges
                .Where(dr => dr.ChangedFields.Any(f => _autoNumberFields.Contains(f))).ToList();

            var checkLevenshtein = CheckLevenshteinDistanceFor(
                allowedAutoChanges,
                new Dictionary<string, Func<Auto, string>>
                {
                    [ChangesPaths.AutoVin] = a => a.Vin
                });
            return checkLevenshtein;
        }

        private bool CheckLevenshteinDistanceFor<T>(
            List<ObjectCompareResult> changes,
            Dictionary<string, Func<T, string>> fieldValues)
            where T : BaseEntity
        {
            return CheckLevenshteinDistanceFor(changes, fieldValues, AllowedLevenshteinDistance);
        }
    }
}

Мне надо проверять что изменение поля inn не более 2х символов, как и для фио