using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AutoDictionaryClient.Code.KaskoClient.Interfaces;
using iRIS.Framework.Common;
using iRIS.Framework.Cqrs;
using iRIS.Framework.Cqrs.Interfaces;
using iRIS.Framework.Data;
using iRIS.Framework.Data.Services.Client.Interfaces;
using iRIS.Framework.Entity.Dictionary.EntitySettings;
using iRIS.Framework.Entity.Dictionary.LocalDictionaryCache;
using iRIS.InsuranceCore;
using iRIS.InsuranceCore.Code.Implementations;
using iRIS.InsuranceCore.Code.Queries;
using iRIS.InsuranceCore.Entities;
using iRIS.InsuranceCore.Infrastructure.Helpers;
using iRIS.InsuranceCore.OSS.Code;
using iRIS.InsuranceCore.OSS.Code.Constants;
using iRIS.InsuranceCore.OSS.Code.Dto.Uni;
using iRIS.InsuranceCore.OSS.Code.Mappers;
using iRIS.KaskoCore.Code.Extensions;
using iRIS.KaskoCore.Code.Helpers;
using iRIS.KaskoCore.CreateOn.Code.Utilities;
using iRIS.KaskoCore.OSS.Code.Mappers.MapToDeal;
using iRIS.KaskoLink2.Code.CreateOn.Extension;
using iRIS.KaskoLink2.Code.CreateOn.Utilities;
using iRIS.KaskoLink2.Code.Implementations.AutoCostCalculator;
using iRIS.KaskoLink2.Code.TypeExtensions;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using OfficeOpenXml.FormulaParsing.Excel.Functions.Text;
using Serilog;

namespace iRIS.KaskoLink2.Code.OSS.Mappers.MapToDeal
{
    public class OssKaskoToDealMapper : OssBaseToDealMapper, IOssKaskoToDealMapper
    {
        private readonly IOssKaskoOptionsParser _ossKaskoOptionsParser;
        private readonly IOssProlongCoefsUpdater _ossProlongCoefUpdater;
        private readonly IAutoCostCalculator _autoCostCalculator;
        private readonly IIntermediaryService _intermediaryService;
        private readonly ICachedQueryServiceInvoker _cachedQueryServiceInvoker;
        private readonly IEntitySettingsManager _entitySettingsManager;

        public OssKaskoToDealMapper(
            IDictionariesCacheManager dictionariesCacheManager,
            IKaskoAutoDictionaryClientV2 autoDictionaryClient,
            ICqrsMediator mediator,
            IOptions<InsuranceCoreModuleConfiguration> option,
            IDaDataHelper daDataHelper,
            IOssKaskoOptionsParser ossKaskoOptionsParser,
            IOssProlongCoefsUpdater ossProlongCoefsUpdater,
            ILogger logger,
            IHttpContextAccessor httpContextAccessor,
            IAutoCostCalculator autoCostCalculator,
            IIntermediaryService intermediaryService,
            ICachedQueryServiceInvoker cachedQueryServiceInvoker,
            IEntitySettingsManager entitySettingsManager)
           : base(dictionariesCacheManager, autoDictionaryClient, mediator, option, daDataHelper, logger, httpContextAccessor)
        {
            _ossKaskoOptionsParser = ossKaskoOptionsParser;
            _ossProlongCoefUpdater = ossProlongCoefsUpdater;
            _cachedQueryServiceInvoker = cachedQueryServiceInvoker;
            _intermediaryService = intermediaryService;
            _entitySettingsManager = entitySettingsManager;
            _autoCostCalculator = autoCostCalculator;
        }

        public override async Task MapAsync(Deal deal, IReadOnlyList<Deal> deals, OssUniPolicyDto viewModel)
        {
            if (viewModel == null)
            {
                return;
            }

            await MapCommonAsync(deal, viewModel);
            await MapAutoCostAsync(deal, viewModel.Vehicle);
            MapOptions(deal, viewModel);
            _ossProlongCoefUpdater.Update(deal, viewModel.Prolcoeff);

            // Маппим дополнительные поля если это сделка ВИ
            MapCoveragesToVIDeal(viewModel.Covers, deal);

            // Посредники
            MapIntermidiary(deal, GetIntermidiaryFromUniOss(viewModel.Participants));
        }

        private void MapCoveragesToVIDeal(List<OssUniCoverDto> propertyDto, Deal deal)
        {
            if (propertyDto == null || (deal.IsVI == null || deal.IsVI == false))
            {
                return;
            }

            var allCoverages = deal.DealConditionGroups
                                       .SelectMany(dcg => dcg.InsuranceObjectConditions.SelectMany(ioc => ioc.DealCoverages))
                                       .Where(dc => dc.ProductCoverage != null)
                                       .ToArray();

            foreach (var cover in propertyDto)
            {
                var existingCoverage = allCoverages.Single(dc => dc.ProductCoverage.CoverageCode == MapHelper.MapForDictionaryValue(cover.Code, MapDictionarys.ProductCoverageCodeToOssCoverCodeMapping));
                if (existingCoverage != null)
                {
                    var franchises = existingCoverage.DealRisks.SelectMany(x => x.ConditionBlock?.DealFranchises ?? new List<DealFranchise>())
                        .Where(x => x != null)
                        .ToList();

                    var risks = existingCoverage.DealRisks.Select(x => x.ProductRisk)
                        .Where(x => x != null)
                        .ToList();

                    foreach (var restriction in cover.Restrictions)
                    {
                        var matchingFranchise = franchises.FirstOrDefault(x => MapHelper.MapForDictionaryKey(x.ProductFranchise.Code, MapDictionarys.ProductCodeToOssRestrictionName) == restriction.Name);
                        if (matchingFranchise != null)
                        {
                            matchingFranchise.Sum = MapHelper.ConvertToDecimal(restriction.Value);
                        }

                        existingCoverage.Sum = MapHelper.ConvertToDecimal(restriction.Value);
                        existingCoverage.ComponentCalculationResult = new ComponentCalculationResult
                        {
                            Value = cover.Premium
                        };
                    }

                    // Удаляем риски, которых нет в DTO
                    //for (int i = existingCoverage.DealRisks.Count - 1; i >= 0; i--)
                    //{
                    //    var dealRisk = existingCoverage.DealRisks[i];
                    //    if (!cover.Risks.Contains(dealRisk.ProductRisk.RiskCode))
                    //    {
                    //        existingCoverage.DealRisks.Remove(dealRisk);
                    //    }
                    //}

                    // Добавление новых рисков из DTO
                    //var currentRiskCodes = existingCoverage.DealRisks.Select(x => x.ProductRisk.RiskCode).ToHashSet();
                    //foreach (var dtoRiskCode in cover.Risks)
                    //{
                    //    if (!currentRiskCodes.Contains(dtoRiskCode))
                    //    {
                    //        var newRisk = new DealRisk
                    //        {
                    //            ProductRisk = new ProductRisk { RiskCode = dtoRiskCode }
                    //        };
                    //        existingCoverage.DealRisks.Add(newRisk);
                    //    }
                    //}
                }
            }
        }

        protected override void MapNumberPolicy(Deal deal, string number, string prevNumber)
        {
            if (deal.IsProlongation() || deal.IsSubscription())
            {
                if (!string.IsNullOrEmpty(number))
                {
                    deal.AutoDeal.PreviousPolicyNumber = number;
                }
            }
            else
            {
                if (!string.IsNullOrEmpty(number))
                {
                    deal.PolicyNumber = number;
                }

                if (!string.IsNullOrEmpty(prevNumber))
                {
                    deal.AutoDeal.PreviousPolicyNumber = prevNumber;
                }
            }
        }

        protected override void MapPremiumCovers(Deal deal, IReadOnlyList<OssUniCoverDto> ossUniCoversVM)
        {
            if (ossUniCoversVM.Count == 0
                    || (deal.IsNotParentDeal() && (!deal.IsVI.HasValue || deal.IsVI == false)))
            {
                return;
            }

            var coversSum = ossUniCoversVM.Sum(s => s.Premium);

            deal.Proposals.Add(new Proposal
            {
                IsSelected = true,
                TotalSum = coversSum
            });
        }

        protected override void ThrowExceptionIfAutoBrandModelNoCorrect(Auto auto)
        {
            base.ThrowExceptionIfAutoBrandModelNoCorrect(auto);

            if (auto.HasOtherBrand()
                || auto.HasOtherModel())
            {
                throw new ArgumentException("Для полиса Каско не может быть значение \"Другая\" для марки/модели авто", nameof(Auto));
            }
        }

        private async Task MapAutoCostAsync(Deal deal, OssUniVehicleDto autoViewModel)
        {
            var insObj = deal.InsuranceObjects.Single(ins => ins.InsuranceObjectTypeCode == OssConstants.InsuranceObjectTypeAutoCode);

            insObj.Auto.AutoCost = MapHelper.ConvertToDecimal(autoViewModel.Price);
            if (deal.IsProlongation())
            {
                var autoCost = await _autoCostCalculator.GetAutoCostAsync(deal);
                insObj.Auto.AutoCost = _autoCostCalculator.RoundAutoCost(autoCost);
            }

            insObj.InsuranceSumm = insObj.Auto.AutoCost;
        }

        private void MapOptions(Deal deal, OssUniPolicyDto viewModel)
        {
            if (deal.IsParentDeal() || (deal.IsVI.HasValue && deal.IsVI == true))
            {
                return;
            }

            var ossKaskoOptions = _ossKaskoOptionsParser.Parse(viewModel);
            if (ossKaskoOptions.Count == 0)
            {
                return;
            }

            var ossKaskoOptionsExcept = ossKaskoOptions.Except(deal.AutoDeal.PreviousOssOptions, new OssKaskoOptionsComparer());
            deal.AutoDeal.PreviousOssOptions.AddRange(ossKaskoOptionsExcept);
        }

        private void MapIntermidiary(Deal deal, OssUniCustomerDto ossUniCustomerViewModel)
        {
            if (ossUniCustomerViewModel == null)
            {
                return;
            }

            var setting = Helpers.EntitySettingHelper.GetSpecialAgregatorForProlongationKaskoSetting(_entitySettingsManager, ossUniCustomerViewModel.CashbookId);

            if (setting == null)
            {
                return;
            }

            var interactionTypeCode = MapInteractionTypeCode(ossUniCustomerViewModel.Role);

            if (string.IsNullOrEmpty(interactionTypeCode))
            {
                return;
            }

            var replacement = _cachedQueryServiceInvoker.GetByExpression<Intermediary>(x => x.Where(i => i.State == VersionState.Current
                    && !i.IsDeleted && i.CashbookId == ossUniCustomerViewModel.CashbookId && i.InteractionTypeCode == interactionTypeCode && i.IntermediaryInternalName == setting.Name)).FirstOrDefault();

            if (replacement != null)
            {
                var productsWithIntermediaries = new Dictionary<Product, List<Intermediary>>
                {
                    { deal.Product, new List<Intermediary> { replacement } }
                };
                _intermediaryService.AddIntermediariesToDeal(deal, productsWithIntermediaries);
            }

            static string MapInteractionTypeCode(int roleId) =>
                roleId switch
                {
                    OssConstants.AgentRoleCode => "Agent",
                    OssConstants.BrokerRoleCode => "Broker",
                    _ => null
                };
        }

        private OssUniCustomerDto GetIntermidiaryFromUniOss(IReadOnlyList<OssUniCustomerDto> customers)
        {
            var bankFromOss = customers.FirstOrDefault(c => c.Role == OssConstants.BrokerRoleCode || c.Role == OssConstants.AgentRoleCode);
            if (bankFromOss == null)
            {
                return null;
            }

            return bankFromOss;
        }
    }
}