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 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 };

        /// <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 (IsInsurantChangedCompletely(insurantChanges))
            {
                resultErrors.Add("Недопустимые изменения: не допускается изменение Страхователя на другое физическое лицо");
                return new ChangeValidationResult(resultErrors);
            }

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

            return new ChangeValidationResult(resultErrors);
        }

        private bool IsInsurantChangedCompletely(Insurant actualInsurant, Insurant expectedInsurant)
        {
            if (actualInsurant == null || expectedInsurant == null)
            {
                return false;
            }

            // Получение объекта Person через метод GetInsurantPerson, предполагая его существование
            var actualPerson = actualInsurant.GetInsurantPerson();
            var expectedPerson = expectedInsurant.GetInsurantPerson();

            // Проверить, изменился ли флаг "IsGettingMarried"
            bool? isInsurantGettingMarriedChanged = null;
            if (actualPerson?.IsMaried != expectedPerson?.IsMaried)
            {
                isInsurantGettingMarriedChanged = actualPerson?.IsMaried;
            }

            var fullNameChanged = InsuranceApp.Infrastructure.Procedures.LevenshteinDistance(
                FormatPersonName(actualInsurant.Subject),
                FormatPersonName(expectedInsurant.Subject)) > 2;

            // Определяем изменения по отдельным полям ФИО
            var lastNameChanged = InsuranceApp.Infrastructure.Procedures.LevenshteinDistance(
                actualPerson.LastName,
                expectedPerson.LastName) > 2;

            var nameChanged = InsuranceApp.Infrastructure.Procedures.LevenshteinDistance(
                actualPerson.Name,
                expectedPerson.Name) > 2;

            var middleNameChanged = InsuranceApp.Infrastructure.Procedures.LevenshteinDistance(
                actualPerson.MiddleName,
                expectedPerson.MiddleName) > 2;

            var dateOfBirthChanged = actualPerson.DateOfBirth != expectedPerson.DateOfBirth;
            var identityDocumentChanged = actualInsurant.IdentityDocument.Series != expectedInsurant.IdentityDocument.Series ||
                                          actualInsurant.IdentityDocument.Number != expectedInsurant.IdentityDocument.Number;

            // Логика принятия решения
            if (!identityDocumentChanged)
            {
                // Если изменили более 2х символов в ФИО или ДР, но паспортные данные не изменились
                return fullNameChanged || dateOfBirthChanged;
            }

            if (identityDocumentChanged)
            {
                if (isInsurantGettingMarriedChanged == true && lastNameChanged && !nameChanged && !middleNameChanged)
                {
                    // Изменения по фамилии при наличии признака "isGettingMarried"
                    return false;
                }

                if (isInsurantGettingMarriedChanged != true)
                {
                    // В случае изменения паспорта без признака "isGettingMarried"
                    return fullNameChanged || dateOfBirthChanged;
                }
            }

            return false;
        }

        /// <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>
        /// <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 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 (!CheckHasAutostartChangesAndKeysAmount(autoChanges))
            {
                resultErrors.Add($"Не допускается изменение признака автозапуска и количества ключей.");
            }

            return new ChangeValidationResult(resultErrors);
        }

        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 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 CheckLevenshteinDistanceFor<T>(
            List<ObjectCompareResult> changes,
            Dictionary<string, Func<T, string>> fieldValues)
            where T : BaseEntity
        {
            return CheckLevenshteinDistanceFor(changes, fieldValues, AllowedLevenshteinDistance);
        }
    }
}

Переделай метод IsInsurantChangedCompletely чтобы он принимал insurantChanges