using System;
using System.Linq;
using System.Web.Security;
using MileageTraker.Web.Models;
using MileageTraker.Web.Utility;

namespace MileageTraker.Web.DAL
{
	public class CodeFirstMembershipProvider : MembershipProvider
	{
		public const string UninitializedPassword = "uninitialized_state";

		#region Properties

		public override string ApplicationName
		{
			get { return GetType().Assembly.GetName().Name; }
			set
			{
				if (value == null) throw new ArgumentNullException("value");
				ApplicationName = GetType().Assembly.GetName().Name;
			}
		}

		public override int MaxInvalidPasswordAttempts
		{
			get { return 5; }
		}

		public override int MinRequiredNonAlphanumericCharacters
		{
			get { return 0; }
		}

		public override int MinRequiredPasswordLength
		{
			get { return 6; }
		}

		public override int PasswordAttemptWindow
		{
			get { return 0; }
		}

		public override MembershipPasswordFormat PasswordFormat
		{
			get { return MembershipPasswordFormat.Hashed; }
		}

		public override string PasswordStrengthRegularExpression
		{
			get { return String.Empty; }
		}

		public override bool RequiresUniqueEmail
		{
			get { return true; }
		}

		#endregion

		#region Functions

		public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion,
		                                          string passwordAnswer, bool isApproved, object providerUserKey,
		                                          out MembershipCreateStatus status)
		{
			if (string.IsNullOrEmpty(username))
			{
				status = MembershipCreateStatus.InvalidUserName;
				return null;
			}

			if (string.IsNullOrEmpty(email))
			{
				status = MembershipCreateStatus.InvalidEmail;
				return null;
			}

			string hashedPassword = null;
			if (password != UninitializedPassword)
				hashedPassword = Crypto.HashPassword(password);
			if (hashedPassword != null && hashedPassword.Length > 128)
			{
				status = MembershipCreateStatus.InvalidPassword;
				return null;
			}

			using (var context = new DataService())
			{
				if (context.FindUserByUsername(username) != null)
				{
					status = MembershipCreateStatus.DuplicateUserName;
					return null;
				}

				if (context.FindUserByUsername(email) != null)
				{
					status = MembershipCreateStatus.DuplicateEmail;
					return null;
				}

				var newUser = new User
					{
						UserId = Guid.NewGuid(),
						Username = username,
						Password = hashedPassword,
						IsApproved = isApproved,
						Email = email,
						Created = DateTime.Now,
						FullName = "Temp Name"
					};

				context.AddUser(newUser);
				status = MembershipCreateStatus.Success;
				return newUser.ToMembershipUser(Membership.Provider.Name);
			}
		}

		public override bool ValidateUser(string username, string password)
		{
			if (string.IsNullOrEmpty(username))
			{
				return false;
			}
			if (string.IsNullOrEmpty(password))
			{
				return false;
			}
			using (var context = new DataService())
			{
				var user = context.FindUserByUsername(username);
				if (user == null)
				{
					return false;
				}
				if (!user.IsApproved)
				{
					throw new UserAccountDisabledException();
				}
				if (user.IsLockedOut)
				{
					throw new UserLockedOutException();
				}
				var hashedPassword = user.Password;

				if (hashedPassword == null)
					throw new UninitializedAccountException();

				var verificationSucceeded =Crypto.VerifyHashedPassword(hashedPassword, password);
				if (verificationSucceeded)
				{
					user.PasswordFailuresSinceLastSuccess = 0;
					user.LastLoginDate = DateTime.Now;
					user.LastActivityDate = DateTime.Now;
				}
				else
				{
					var failures = user.PasswordFailuresSinceLastSuccess;
					if (failures < MaxInvalidPasswordAttempts)
					{
						user.PasswordFailuresSinceLastSuccess += 1;
						user.LastPasswordFailureDate = DateTime.Now;
					}
					else if (failures >= MaxInvalidPasswordAttempts)
					{
						user.LastPasswordFailureDate = DateTime.Now;
						user.LastLockoutDate = DateTime.Now;
						user.IsLockedOut = true;
					}
				}
				context.UpdateUser(user);
				return verificationSucceeded;
			}
		}

		public override MembershipUser GetUser(string username, bool userIsOnline)
		{
			if (string.IsNullOrEmpty(username))
			{
				return null;
			}
			using (var context = new DataService())
			{
				var user = context.FindUserByUsername(username);
				if (user == null)
				{
					return null;
				}
				
				if (userIsOnline)
				{
					user.LastActivityDate = DateTime.Now;
					context.UpdateUser(user);
				}
				return user.ToMembershipUser(System.Web.Security.Membership.Provider.Name);
			}
		}

		public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
		{
			if (!(providerUserKey is Guid))
			{
				return null;
			}

			using (var context = new DataService())
			{
				var user = context.GetUser((Guid)providerUserKey);
				if (user == null)
				{
					return null;
				}

				if (userIsOnline)
				{
					user.LastActivityDate = DateTime.Now;
					context.UpdateUser(user);
				}
				return user.ToMembershipUser(System.Web.Security.Membership.Provider.Name);
			}
		}

		public override bool ChangePassword(string username, string currentPassword, string newPassword)
		{
			if (string.IsNullOrEmpty(username))
			{
				return false;
			}
			if (string.IsNullOrEmpty(currentPassword))
			{
				return false;
			}
			if (string.IsNullOrEmpty(newPassword))
			{
				return false;
			}
			using (var context = new DataService())
			{
				var user = context.FindUserByUsername(username);
				if (user == null)
				{
					return false;
				}
				var hashedPassword = user.Password;
				var verificationSucceeded = (hashedPassword != null && Crypto.VerifyHashedPassword(hashedPassword, currentPassword));
				if (verificationSucceeded)
				{
					user.PasswordFailuresSinceLastSuccess = 0;
				}
				else
				{
					var failures = user.PasswordFailuresSinceLastSuccess;
					if (failures < MaxInvalidPasswordAttempts)
					{
						user.PasswordFailuresSinceLastSuccess += 1;
						user.LastPasswordFailureDate = DateTime.Now;
					}
					else if (failures >= MaxInvalidPasswordAttempts)
					{
						user.LastPasswordFailureDate = DateTime.Now;
						user.LastLockoutDate = DateTime.Now;
						user.IsLockedOut = true;
					}
					context.UpdateUser(user);
					return false;
				}
				var newHashedPassword = Crypto.HashPassword(newPassword);
				if (newHashedPassword.Length > 128)
				{
					return false;
				}
				user.Password = newHashedPassword;
				user.LastPasswordChangedDate = DateTime.Now;
				context.UpdateUser(user);
				return true;
			}
		}

		public override bool UnlockUser(string userName)
		{
			using (var context = new DataService())
			{
				var user = context.FindUserByUsername(userName);
				if (user != null)
				{
					user.IsLockedOut = false;
					user.PasswordFailuresSinceLastSuccess = 0;
					context.UpdateUser(user);
					return true;
				}
				return false;
			}
		}

		public override int GetNumberOfUsersOnline()
		{
			var dateActive = CustomExtensions.UserOnlineThreshold();
			using (var context = new DataService())
			{
				return context.GetUsers().Count(usr => usr.LastActivityDate > dateActive);
			}
		}

		public override bool DeleteUser(string username, bool deleteAllRelatedData)
		{
			throw new NotSupportedException();
		}

		public override string GetUserNameByEmail(string email)
		{
			using (var context = new DataService())
			{
				var user = context.FindUserByEmail(email);
				if (user != null)
				{
					return user.Username;
				}
				return string.Empty;
			}
		}

		public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize,
		                                                          out int totalRecords)
		{
			var membershipUsers = new MembershipUserCollection();
			using (var context = new DataService())
			{
				totalRecords = context.GetUsers().Count(user => user.Email == emailToMatch);
				var users =
					context.GetUsers().Where(usr => usr.Email == emailToMatch)
					       .OrderBy(usrn => usrn.Username)
					       .Skip(pageIndex*pageSize)
					       .Take(pageSize);
				foreach (var user in users)
				{
					membershipUsers.Add(
						user.ToMembershipUser(Membership.Provider.Name));
				}
			}
			return membershipUsers;
		}

		public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize,
		                                                         out int totalRecords)
		{
			var membershipUsers = new MembershipUserCollection();
			using (var context = new DataService())
			{
				totalRecords = context.GetUsers().Count(usr => usr.Username == usernameToMatch);
				var users =
					context.GetUsers().Where(usr => usr.Username == usernameToMatch)
					       .OrderBy(usrn => usrn.Username)
					       .Skip(pageIndex*pageSize)
					       .Take(pageSize);
				foreach (var user in users)
				{
					membershipUsers.Add(user.ToMembershipUser(Membership.Provider.Name));
				}
			}
			return membershipUsers;
		}

		public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
		{
			var membershipUsers = new MembershipUserCollection();
			using (var context = new DataService())
			{
				totalRecords = context.GetUsers().Count();
				var users =
					context.GetUsers()
					.OrderBy(usrn => usrn.Username)
					.Skip(pageIndex*pageSize)
					.Take(pageSize);

				foreach (var user in users)
				{
					membershipUsers.Add(user.ToMembershipUser(Membership.Provider.Name));
				}
			}
			return membershipUsers;
		}

		//public string CreateAccount(string userName, string password, bool requireConfirmationToken)
		//{
		//	if (string.IsNullOrEmpty(userName))
		//	{
		//		throw new MembershipCreateUserException(MembershipCreateStatus.InvalidUserName);
		//	}

		//	if (string.IsNullOrEmpty(password))
		//	{
		//		throw new MembershipCreateUserException(MembershipCreateStatus.InvalidPassword);
		//	}

		//	var hashedPassword = Crypto.HashPassword(password);
		//	if (hashedPassword.Length > 128)
		//	{
		//		throw new MembershipCreateUserException(MembershipCreateStatus.InvalidPassword);
		//	}

		//	using (var context = new MileageTrakerContext())
		//	{
		//		if (context.Users.Any(usr => usr.Username == userName))
		//		{
		//			throw new MembershipCreateUserException(MembershipCreateStatus.DuplicateUserName);
		//		}

		//		var token = string.Empty;
		//		if (requireConfirmationToken)
		//		{
		//			token = Algorithms.GenerateToken();
		//		}

		//		var newUser = new User
		//			{
		//				UserId = Guid.NewGuid(),
		//				Username = userName,
		//				Password = hashedPassword,
		//				IsApproved = !requireConfirmationToken,
		//				Email = string.Empty,
		//				Created = DateTime.Now,
		//				LastPasswordChangedDate = DateTime.Now,
		//				PasswordFailuresSinceLastSuccess = 0,
		//				LastLoginDate = SqlDateTime.MinValue.Value,
		//				LastActivityDate = SqlDateTime.MinValue.Value,
		//				LastLockoutDate = SqlDateTime.MinValue.Value,
		//				IsLockedOut = false,
		//				LastPasswordFailureDate = SqlDateTime.MinValue.Value
		//			};

		//		context.Users.Add(newUser);
		//		context.SaveChanges();
		//		return token;
		//	}
		//}

		#endregion

		#region Not Supported

		//CodeFirstMembershipProvider does not support password retrieval scenarios.
		public override bool EnablePasswordRetrieval
		{
			get { return false; }
		}

		//CodeFirstMembershipProvider does not support password reset scenarios.
		public override bool EnablePasswordReset
		{
			get { return false; }
		}

		//CodeFirstMembershipProvider does not support question and answer scenarios.
		public override bool RequiresQuestionAndAnswer
		{
			get { return false; }
		}

		public override string GetPassword(string username, string answer)
		{
			throw new NotSupportedException("Consider using methods from WebSecurity module.");
		}

		public override string ResetPassword(string username, string answer)
		{
			throw new NotSupportedException("Consider using methods from WebSecurity module.");
		}

		public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion,
		                                                     string newPasswordAnswer)
		{
			throw new NotSupportedException("Consider using methods from WebSecurity module.");
		}

		//CodeFirstMembershipProvider does not support UpdateUser because this method is useless.
		public override void UpdateUser(MembershipUser user)
		{
			throw new NotSupportedException();
		}

		#endregion
	}
}