using System.Security.Cryptography.X509Certificates;
using BankContracts.BindingModels;
using BankContracts.SearchModels;
using BankContracts.StoragesContracts;
using BankContracts.ViewModels;
using BankDatabaseImplement.Models;
using Microsoft.EntityFrameworkCore;

namespace BankDatabaseImplement.Implements
{
    public class PurchaseStorage : IPurchaseStorage
    {
        private void CheckSearchModel(PurchaseSearchModel model)
        {
            if (model == null)
                throw new ArgumentNullException("Передаваемая модель для поиска равна нулю", nameof(model));
            if (!model.Id.HasValue && !model.ClientId.HasValue && !model.DateFrom.HasValue && !model.DateTo.HasValue && model.OperationsIds == null)
                throw new ArgumentException("Все передаваемые поля поисковой модели оказались пусты или равны null");
            if (model.DateFrom.HasValue != model.DateTo.HasValue)
                throw new ArgumentException($"Не указано начало {model.DateFrom} или конец {model.DateTo} периода для поиска по дате.");
        }
        public PurchaseViewModel? Delete(PurchaseBindingModel model)
        {
            using var context = new BankDB();
            var element = context.Purchases.FirstOrDefault(x => x.Id == model.Id);
            if (element != null)
            {
                context.Purchases.Remove(element);
                context.SaveChanges();
                return element.GetViewModel;
            }
            return null;
        }


        public List<PaymentViewModel> GetPaymentsFromPurchaseAndOperation(PurchaseSearchModel modelPurchase, OperationSearchModel modelOperation)
        {
            if (!modelPurchase.Id.HasValue)
            {
                throw new ArgumentNullException(nameof(modelPurchase), "Получена поисковая модель без Id");
            }
            if (!modelOperation.Id.HasValue)
            {
                throw new ArgumentNullException(nameof(modelOperation), "Получена поисковая модель без Id");
            }
            using var context = new BankDB();
            var operationByPurchase = context.OperationByPurchases
                .Include(x => x.Payments)
                .FirstOrDefault(x => x.OperationId == modelOperation.Id && x.PurchaseId == modelPurchase.Id);
            if (operationByPurchase?.Payments == null)
            {
                throw new InvalidOperationException(
                    $"Не существует связи между данной операции(Id={modelOperation.Id}) и покупкой(Id={modelPurchase.Id})");
            }
            return operationByPurchase.Payments.Select(payment => payment.GetViewModel).ToList();
        }

        public PurchaseViewModel? GetElement(PurchaseSearchModel model)
        {
            using var context = new BankDB();
            if (!model.Id.HasValue)
            {
                return null;
            }
            return context.Purchases
                .Include(x => x.Client)
                .Include(x => x.Operations)
                .FirstOrDefault(x => model.Id.HasValue && x.Id == model.Id)?.GetViewModel;
        }

        public List<PurchaseViewModel> GetFilteredList(PurchaseSearchModel model)
        {
            CheckSearchModel(model);
            if (model.Id.HasValue)
            {
                var res = GetElement(model);
                return res != null ? new() { res } : new();
            }

            using var context = new BankDB();

            var query = context.Purchases.Include(x => x.Client);
            IQueryable<Purchase>? resultQuery = null;
            if (model.ClientId.HasValue)
            {

                return query
                    .Where(x => model.ClientId == x.ClientId)
                    .Select(x => x.GetViewModel)
                    .ToList();
            }

            if (model.DateTo.HasValue)
                resultQuery = query
                    .Include(x => x.Operations)
                    .ThenInclude(x => x.Operation)
                    .Include(x => x.Costs)!
                    .ThenInclude(x => x.Cost)
                    .Where(x => model.DateFrom <= x.DatePurchase && x.DatePurchase <= model.DateTo);

            else if (model.OperationsIds != null)
                resultQuery = query
                    .Where(x => x.Operations.Any(x => model.OperationsIds.Contains(x.OperationId)))
                    .Include(x => x.Operations)
                    .ThenInclude(x => x.Operation);

            return resultQuery?
                .Select(x => x.GetViewModel)
                .ToList() ?? new();
        }

        public List<PurchaseViewModel> GetFullList()
        {
            using var context = new BankDB();
            return context.Purchases
                .Include(x => x.Client)
                .Include(x => x.Operations)
                .Select(x => x.GetViewModel)
                .ToList();
        }

        public PurchaseViewModel? Insert(PurchaseBindingModel model)
        {
            var newPurchase = Purchase.Create(model);
            if (newPurchase == null)
            {
                return null;
            }
            using var context = new BankDB();
            context.Purchases.Add(newPurchase);
            context.SaveChanges();
            newPurchase.UpdateOperations(context, model);
            context.SaveChanges();
            return newPurchase.GetViewModel;
        }

        public PurchaseViewModel? Update(PurchaseBindingModel model)
        {
            using var context = new BankDB();
            var purchase = context.Purchases.FirstOrDefault(x => x.Id == model.Id);
            if (purchase == null)
            {
                return null;
            }
            purchase.Update(model);
            purchase.UpdateOperations(context, model);
            context.SaveChanges();
            return purchase.GetViewModel;
        }
    }
}
