using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml;
using SmartStore.Core.Domain.Customers;
using SmartStore.Core.Domain.Orders;
using SmartStore.Core.Infrastructure;
using SmartStore.Services.Common;
using SmartStore.Services.Localization;
using SmartStore.Services.Orders;

namespace SmartStore.Services.Customers
{
    public static class CustomerExtentions
    {
        /// <summary>
        /// Gets a value indicating whether customer is in a certain customer role
        /// </summary>
        /// <param name="customer">Customer</param>
        /// <param name="customerRoleSystemName">Customer role system name</param>
        /// <param name="onlyActiveCustomerRoles">A value indicating whether we should look only in active customer roles</param>
        /// <returns>Result</returns>
        public static bool IsInCustomerRole(this Customer customer,
            string customerRoleSystemName, bool onlyActiveCustomerRoles = true)
        {
            if (customer == null)
                throw new ArgumentNullException("customer");

            if (String.IsNullOrEmpty(customerRoleSystemName))
                throw new ArgumentNullException("customerRoleSystemName");

            var result = customer.CustomerRoles
                .Where(cr => !onlyActiveCustomerRoles || cr.Active)
                .Where(cr => cr.SystemName == customerRoleSystemName)
                .FirstOrDefault() != null;
            return result;
        }

        /// <summary>
        /// Gets a value indicating whether the customer is a built-in record for background tasks
        /// </summary>
        /// <param name="customer">Customer</param>
        /// <returns>Result</returns>
        public static bool IsBackgroundTaskAccount(this Customer customer)
        {
            if (customer == null)
                throw new ArgumentNullException("customer");

            if (!customer.IsSystemAccount || String.IsNullOrEmpty(customer.SystemName))
                return false;

            var result = customer.SystemName.Equals(SystemCustomerNames.BackgroundTask, StringComparison.InvariantCultureIgnoreCase);
            return result;
        }

        /// <summary>
        /// Gets a value indicating whether customer a search engine
        /// </summary>
        /// <param name="customer">Customer</param>
        /// <returns>Result</returns>
        public static bool IsSearchEngineAccount(this Customer customer)
        {
            if (customer == null)
                throw new ArgumentNullException("customer");

            if (!customer.IsSystemAccount || String.IsNullOrEmpty(customer.SystemName))
                return false;

            var result = customer.SystemName.Equals(SystemCustomerNames.SearchEngine, StringComparison.InvariantCultureIgnoreCase);
            return result;
        }

        /// <summary>
        /// Gets a value indicating whether customer is administrator
        /// </summary>
        /// <param name="customer">Customer</param>
        /// <param name="onlyActiveCustomerRoles">A value indicating whether we should look only in active customer roles</param>
        /// <returns>Result</returns>
        public static bool IsAdmin(this Customer customer, bool onlyActiveCustomerRoles = true)
        {
            return IsInCustomerRole(customer, SystemCustomerRoleNames.Administrators, onlyActiveCustomerRoles);
        }

		/// <summary>
		/// Gets a value indicating whether customer is super administrator
		/// </summary>
		/// <remarks>codehint: sm-add</remarks>
		/// <param name="customer">Customer</param>
		/// <param name="onlyActiveCustomerRoles">A value indicating whether we should look only in active customer roles</param>
		/// <returns>Result</returns>
		public static bool IsSuperAdmin(this Customer customer, bool onlyActiveCustomerRoles = true) {
			return IsInCustomerRole(customer, SystemCustomerRoleNames.SuperAdministrators, onlyActiveCustomerRoles);
		}

        /// <summary>
        /// Gets a value indicating whether customer is a forum moderator
        /// </summary>
        /// <param name="customer">Customer</param>
        /// <param name="onlyActiveCustomerRoles">A value indicating whether we should look only in active customer roles</param>
        /// <returns>Result</returns>
        public static bool IsForumModerator(this Customer customer, bool onlyActiveCustomerRoles = true)
        {
            return IsInCustomerRole(customer, SystemCustomerRoleNames.ForumModerators, onlyActiveCustomerRoles);
        }

        /// <summary>
        /// Gets a value indicating whether customer is registered
        /// </summary>
        /// <param name="customer">Customer</param>
        /// <param name="onlyActiveCustomerRoles">A value indicating whether we should look only in active customer roles</param>
        /// <returns>Result</returns>
        public static bool IsRegistered(this Customer customer, bool onlyActiveCustomerRoles = true)
        {
            return IsInCustomerRole(customer, SystemCustomerRoleNames.Registered, onlyActiveCustomerRoles);
        }

        /// <summary>
        /// Gets a value indicating whether customer is guest
        /// </summary>
        /// <param name="customer">Customer</param>
        /// <param name="onlyActiveCustomerRoles">A value indicating whether we should look only in active customer roles</param>
        /// <returns>Result</returns>
        public static bool IsGuest(this Customer customer, bool onlyActiveCustomerRoles = true)
        {
            return IsInCustomerRole(customer, SystemCustomerRoleNames.Guests, onlyActiveCustomerRoles);
        }
        
        public static string GetFullName(this Customer customer)
        {
            if (customer == null)
                throw new ArgumentNullException("customer");
            var firstName = customer.GetAttribute<string>(SystemCustomerAttributeNames.FirstName);
            var lastName = customer.GetAttribute<string>(SystemCustomerAttributeNames.LastName);

            string fullName = "";
            if (!String.IsNullOrWhiteSpace(firstName) && !String.IsNullOrWhiteSpace(lastName))
            {
                fullName = string.Format("{0} {1}", firstName, lastName);
            }
            else
            {
                if (!String.IsNullOrWhiteSpace(firstName))
                    fullName = firstName;

                if (!String.IsNullOrWhiteSpace(lastName))
                    fullName = lastName;

                if (String.IsNullOrWhiteSpace(firstName) && String.IsNullOrWhiteSpace(lastName))
                {
                    var address = customer.Addresses.FirstOrDefault();
                    if (address != null)
                        fullName = string.Format("{0} {1}", address.FirstName, address.LastName);
                }
            }
            return fullName;
        }

        /// <summary>
        /// Formats the customer name
        /// </summary>
        /// <param name="customer">Source</param>
        /// <returns>Formatted text</returns>
        public static string FormatUserName(this Customer customer)
        {
            return FormatUserName(customer, false);
        }

        /// <summary>
        /// Formats the customer name
        /// </summary>
        /// <param name="customer">Source</param>
        /// <param name="stripTooLong">Strip too long customer name</param>
        /// <returns>Formatted text</returns>
        public static string FormatUserName(this Customer customer, bool stripTooLong)
        {
            if (customer == null)
                return string.Empty;

            if (customer.IsGuest())
            {
                return EngineContext.Current.Resolve<ILocalizationService>().GetResource("Customer.Guest");
            }

            string result = string.Empty;
            switch (EngineContext.Current.Resolve<CustomerSettings>().CustomerNameFormat)
            {
                case CustomerNameFormat.ShowEmails:
                    result = customer.Email;
                    break;
                case CustomerNameFormat.ShowFullNames:
                    result = customer.GetFullName();
                    break;
                case CustomerNameFormat.ShowUsernames:
                    result = customer.Username;
                    break;
				case CustomerNameFormat.ShowFirstName:
					result = customer.GetAttribute<string>(SystemCustomerAttributeNames.FirstName);
					break;
				case CustomerNameFormat.ShowNameAndCity:
					{
						var firstName = customer.GetAttribute<string>(SystemCustomerAttributeNames.FirstName);
						var lastName = customer.GetAttribute<string>(SystemCustomerAttributeNames.LastName);
						var city = customer.GetAttribute<string>(SystemCustomerAttributeNames.City);

						if (firstName.IsNullOrEmpty())
						{
							var address = customer.Addresses.FirstOrDefault();
							if (address != null)
							{
								firstName = address.FirstName;
								lastName = address.LastName;
								city = address.City;
							}
						}

						result = firstName;
						if (lastName.HasValue())
						{
							result = "{0} {1}.".FormatWith(result, lastName.First());
						}

						if (city.HasValue())
						{
							var from = EngineContext.Current.Resolve<ILocalizationService>().GetResource("Common.ComingFrom");
							result = "{0} {1} {2}".FormatWith(result, from, city);
						}
					}
					break;
                default:
                    break;
            }

            if (stripTooLong && result.HasValue())
            {
                int maxLength = EngineContext.Current.Resolve<CustomerSettings>().CustomerNameFormatMaxLength;
                if (maxLength > 0 && result.Length > maxLength)
                {
					result = result.Truncate(maxLength, "...");
                }
            }

            return result;
        }

		/// <summary>
		/// Find any email address of customer
		/// </summary>
		public static string FindEmail(this Customer customer)
		{
			if (customer != null)
			{
				if (customer.Email.HasValue())
					return customer.Email;

				if (customer.BillingAddress != null && customer.BillingAddress.Email.HasValue())
					return customer.BillingAddress.Email;

				if (customer.ShippingAddress != null && customer.ShippingAddress.Email.HasValue())
					return customer.ShippingAddress.Email;
			}
			return null;
		}


		#region Shopping cart

		public static int CountProductsInCart(this Customer customer, ShoppingCartType cartType, int? storeId = null)
		{
			int count = customer.ShoppingCartItems
				.Filter(cartType, storeId)
				.Where(x => x.ParentItemId == null)
				.Sum(x => x.Quantity);

			return count;
		}
		public static List<OrganizedShoppingCartItem> GetCartItems(this Customer customer, ShoppingCartType cartType, int? storeId = null, bool orderById = false)
		{
			var rawItems = customer.ShoppingCartItems.Filter(cartType, storeId);

			if (orderById)
				rawItems = rawItems.OrderByDescending(x => x.Id);

			var organizedItems = rawItems.ToList().Organize();

			return organizedItems.ToList();
		}

		#endregion

		#region Gift cards

		/// <summary>
		/// Gets coupon codes
		/// </summary>
		/// <param name="customer">Customer</param>
		/// <returns>Coupon codes</returns>
		public static string[] ParseAppliedGiftCardCouponCodes(this Customer customer)
		{
			var genericAttributeService = EngineContext.Current.Resolve<IGenericAttributeService>();
			string existingGiftCartCouponCodes = customer.GetAttribute<string>(SystemCustomerAttributeNames.GiftCardCouponCodes,
				genericAttributeService);

			var couponCodes = new List<string>();
			if (String.IsNullOrEmpty(existingGiftCartCouponCodes))
				return couponCodes.ToArray();

			try
			{
				var xmlDoc = new XmlDocument();
				xmlDoc.LoadXml(existingGiftCartCouponCodes);

				var nodeList1 = xmlDoc.SelectNodes(@"//GiftCardCouponCodes/CouponCode");
				foreach (XmlNode node1 in nodeList1)
				{
					if (node1.Attributes != null && node1.Attributes["Code"] != null)
					{
						string code = node1.Attributes["Code"].InnerText.Trim();
						couponCodes.Add(code);
					}
				}
			}
			catch (Exception exc)
			{
				Debug.Write(exc.ToString());
			}
			return couponCodes.ToArray();
		}

		/// <summary>
		/// Adds a coupon code
		/// </summary>
		/// <param name="customer">Customer</param>
		/// <param name="couponCode">Coupon code</param>
		/// <returns>New coupon codes document</returns>
		public static void ApplyGiftCardCouponCode(this Customer customer, string couponCode)
		{
			var genericAttributeService = EngineContext.Current.Resolve<IGenericAttributeService>();
			string result = string.Empty;
			try
			{
				string existingGiftCartCouponCodes = customer.GetAttribute<string>(SystemCustomerAttributeNames.GiftCardCouponCodes,
					genericAttributeService);

				couponCode = couponCode.Trim().ToLower();

				var xmlDoc = new XmlDocument();
				if (String.IsNullOrEmpty(existingGiftCartCouponCodes))
				{
					var element1 = xmlDoc.CreateElement("GiftCardCouponCodes");
					xmlDoc.AppendChild(element1);
				}
				else
				{
					xmlDoc.LoadXml(existingGiftCartCouponCodes);
				}
				var rootElement = (XmlElement)xmlDoc.SelectSingleNode(@"//GiftCardCouponCodes");

				XmlElement gcElement = null;
				//find existing
				var nodeList1 = xmlDoc.SelectNodes(@"//GiftCardCouponCodes/CouponCode");
				foreach (XmlNode node1 in nodeList1)
				{
					if (node1.Attributes != null && node1.Attributes["Code"] != null)
					{
						string couponCodeAttribute = node1.Attributes["Code"].InnerText.Trim();
						if (couponCodeAttribute.ToLower() == couponCode.ToLower())
						{
							gcElement = (XmlElement)node1;
							break;
						}
					}
				}

				//create new one if not found
				if (gcElement == null)
				{
					gcElement = xmlDoc.CreateElement("CouponCode");
					gcElement.SetAttribute("Code", couponCode);
					rootElement.AppendChild(gcElement);
				}

				result = xmlDoc.OuterXml;
			}
			catch (Exception exc)
			{
				Debug.Write(exc.ToString());
			}

			//apply new value
			genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.GiftCardCouponCodes, result);
		}

		/// <summary>
		/// Removes a coupon code
		/// </summary>
		/// <param name="customer">Customer</param>
		/// <param name="couponCode">Coupon code to remove</param>
		/// <returns>New coupon codes document</returns>
		public static void RemoveGiftCardCouponCode(this Customer customer, string couponCode)
		{
			//get applied coupon codes
			var existingCouponCodes = customer.ParseAppliedGiftCardCouponCodes();

			//clear them
			var genericAttributeService = EngineContext.Current.Resolve<IGenericAttributeService>();
			genericAttributeService.SaveAttribute<string>(customer, SystemCustomerAttributeNames.GiftCardCouponCodes, null);

			//save again except removed one
			foreach (string existingCouponCode in existingCouponCodes)
				if (!existingCouponCode.Equals(couponCode, StringComparison.InvariantCultureIgnoreCase))
					customer.ApplyGiftCardCouponCode(existingCouponCode);
		}

		#endregion
    }
}
