using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DiplomovaPrace.Contracts;
using DiplomovaPrace.Contracts.Addresses;
using DiplomovaPrace.DataLayer.Repositories.Orders;
using DiplomovaPrace.DataLayer.Repositories.Security;
using DiplomovaPrace.Model.Orders;
using DiplomovaPrace.Model.Security;
using Havit.Data.Patterns.UnitOfWorks;

namespace DiplomovaPrace.Facades.Addresses
{
	public class AddressFacade : IAddressFacadeContract
	{
		private readonly IAddressRepository addressRepository;
		private readonly ILoginAccountRepository loginAccountRepository;
		private readonly IOrderRepository orderRepository;
		private readonly IUnitOfWork unitOfWork;

		public AddressFacade(IAddressRepository addressRepository,
			ILoginAccountRepository loginAccountRepository,
			IOrderRepository orderRepository,
			IUnitOfWork unitOfWork)
		{
			this.addressRepository = addressRepository;
			this.loginAccountRepository = loginAccountRepository;
			this.orderRepository = orderRepository;
			this.unitOfWork = unitOfWork;
		}

		public async Task<List<AddressDto>> GetDeliveryAddressesByUserAsync(ValueDto<string> user)
		{
			LoginAccount loginAccount = await loginAccountRepository.GetByEmailAsync(user.Value).ConfigureAwait(false);
			List<Address> addresses = await addressRepository.GetAddressesByUserIDAsync(loginAccount.ID).ConfigureAwait(false);

			return addresses
				.Where(i => i.AddressTypeID == (int)Contracts.Addresses.AddressType.DeliveryAddress)
				.Select(i => new AddressDto
				{
					ID = i.ID,
					FirstName = i.FirstName,
					LastName = i.LastName,
					PhoneNumber = i.PhoneNumber,
					City = i.City,
					Street = i.Street,
					ZipCode = i.ZipCode,
					State = i.State
				}).ToList();
		}

		public async Task<AddressDto> GetDeliveryAddressesByIDAsync(ValueDto<int> id)
		{
			Address address = await addressRepository.GetObjectAsync(id.Value);

			if (address is not null)
			{
				return new AddressDto()
				{
					ID = address.ID,
					FirstName = address.FirstName,
					LastName = address.LastName,
					PhoneNumber = address.PhoneNumber,
					City = address.City,
					Street = address.Street,
					ZipCode = address.ZipCode,
					State = address.State
				};
			}

			return new AddressDto();
		}

		public async Task<List<AddressDto>> GetInvoicingAddressesByUserAsync(ValueDto<string> user)
		{
			LoginAccount loginAccount = await loginAccountRepository.GetByEmailAsync(user.Value).ConfigureAwait(false);
			List<Address> addresses = await addressRepository.GetAddressesByUserIDAsync(loginAccount.ID).ConfigureAwait(false);

			return addresses
				.Where(i => i.AddressTypeID == (int)Contracts.Addresses.AddressType.InvoicingAddress)
				.Select(i => new AddressDto
				{
					ID = i.ID,
					FirstName = i.FirstName,
					LastName = i.LastName,
					PhoneNumber = i.PhoneNumber,
					City = i.City,
					Street = i.Street,
					ZipCode = i.ZipCode,
					State = i.State
				}).ToList();
		}

		public async Task<AddressDto> GetInvoicingAddressesByIDAsync(ValueDto<int> id)
		{
			Address address = await addressRepository.GetObjectAsync(id.Value);

			if (address is not null)
			{
				return new AddressDto()
				{
					ID = address.ID,
					FirstName = address.FirstName,
					LastName = address.LastName,
					PhoneNumber = address.PhoneNumber,
					City = address.City,
					Street = address.Street,
					ZipCode = address.ZipCode,
					State = address.State
				};
			}

			return new AddressDto();
		}

		public async Task<AddressDto> CreateUserAddressAsync(AddressDto inputModel)
		{
			LoginAccount loginAccount = await loginAccountRepository.GetByEmailAsync(inputModel.UserEmail);

			if (loginAccount is null)
			{
				return new AddressDto();
			}

			Address address = new()
			{
				FirstName = inputModel.FirstName,
				LastName = inputModel.LastName,
				PhoneNumber = inputModel.PhoneNumber,
				City = inputModel.City,
				Street = inputModel.Street,
				ZipCode = inputModel.ZipCode,
				State = inputModel.State,
				AddressTypeID = inputModel.AddressTypeID,
				LoginAccountID = loginAccount.ID
			};

			unitOfWork.AddForInsert(address);
			await unitOfWork.CommitAsync();

			return new AddressDto
			{
				ID = address.ID,
				FirstName = address.FirstName,
				LastName = address.LastName,
				PhoneNumber = address.PhoneNumber,
				City = address.City,
				Street = address.Street,
				ZipCode = address.ZipCode,
				State = address.State,
				AddressTypeID = address.AddressTypeID
			};
		}

		// create address without login account
		public async Task<AddressDto> CreateAddressAsync(AddressDto inputModel)
		{
			Address address = new()
			{
				FirstName = inputModel.FirstName,
				LastName = inputModel.LastName,
				PhoneNumber = inputModel.PhoneNumber,
				City = inputModel.City,
				Street = inputModel.Street,
				ZipCode = inputModel.ZipCode,
				State = inputModel.State,
				AddressTypeID = inputModel.AddressTypeID,
			};

			unitOfWork.AddForInsert(address);
			await unitOfWork.CommitAsync();

			return new AddressDto
			{
				ID = address.ID,
				FirstName = address.FirstName,
				LastName = address.LastName,
				PhoneNumber = address.PhoneNumber,
				City = address.City,
				Street = address.Street,
				ZipCode = address.ZipCode,
				State = address.State,
				AddressTypeID = address.AddressTypeID
			};
		}

		public async Task<AddressDto> UpdateUserAddressAsync(AddressDto inputModel)
		{
			Address address = await addressRepository.GetObjectAsync(inputModel.ID);

			if (address is null)
			{
				return new AddressDto();
			}

			List<Order> orders = await orderRepository.GetAllByAddressIDAsync(address.ID);

			// if there are any orders then create new address
			if (orders is not null || orders.Count > 0)
			{
				Address newAddress = new()
				{
					FirstName = inputModel.FirstName,
					LastName = inputModel.LastName,
					PhoneNumber = inputModel.PhoneNumber,
					City = inputModel.City,
					Street = inputModel.Street,
					ZipCode = inputModel.ZipCode,
					State = inputModel.State,
					AddressTypeID = address.AddressTypeID,
					LoginAccountID = address.LoginAccountID
				};

				unitOfWork.AddForInsert(newAddress);
				unitOfWork.AddForDelete(address);
				await unitOfWork.CommitAsync();

				return new AddressDto
				{
					ID = newAddress.ID,
					FirstName = newAddress.FirstName,
					LastName = newAddress.LastName,
					PhoneNumber = newAddress.PhoneNumber,
					City = newAddress.City,
					Street = newAddress.Street,
					ZipCode = newAddress.ZipCode,
					State = newAddress.State,
					AddressTypeID = newAddress.AddressTypeID
				};
			}

			// update address
			if (!address.FirstName.Equals(inputModel.FirstName) || !address.LastName.Equals(inputModel.LastName) || !address.PhoneNumber.Equals(inputModel.PhoneNumber) ||
				!address.City.Equals(inputModel.City) || !address.Street.Equals(inputModel.Street) || !address.ZipCode.Equals(inputModel.ZipCode) ||
				!address.State.Equals(inputModel.State))
			{
				address.FirstName = inputModel.FirstName;
				address.LastName = inputModel.LastName;
				address.PhoneNumber = inputModel.PhoneNumber;
				address.City = inputModel.City;
				address.Street = inputModel.Street;
				address.ZipCode = inputModel.ZipCode;
				address.State = inputModel.State;

				unitOfWork.AddForUpdate(address);
				await unitOfWork.CommitAsync();

				return new AddressDto
				{
					ID = address.ID,
					FirstName = address.FirstName,
					LastName = address.LastName,
					PhoneNumber = address.PhoneNumber,
					City = address.City,
					Street = address.Street,
					ZipCode = address.ZipCode,
					State = address.State,
					AddressTypeID = address.AddressTypeID
				};
			}

			return new AddressDto();
		}

		// Update address withou login account
		public async Task<AddressDto> UpdateAddressAsync(AddressDto inputModel)
		{
			Address address = await addressRepository.GetObjectAsync(inputModel.ID);

			if (address is not null)
			{
				address.FirstName = inputModel.FirstName;
				address.LastName = inputModel.LastName;
				address.PhoneNumber = inputModel.PhoneNumber;
				address.City = inputModel.City;
				address.Street = inputModel.Street;
				address.ZipCode = inputModel.ZipCode;
				address.State = inputModel.State;

				unitOfWork.AddForUpdate(address);
				await unitOfWork.CommitAsync();

				return new AddressDto
				{
					ID = address.ID,
					FirstName = address.FirstName,
					LastName = address.LastName,
					PhoneNumber = address.PhoneNumber,
					City = address.City,
					Street = address.Street,
					ZipCode = address.ZipCode,
					State = address.State,
					AddressTypeID = address.AddressTypeID
				};
			}

			return new AddressDto();
		}
	}
}
