using TechHelper.Context;
using TechHelper.Services;
using Entities.DTO;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using TechHelper.Features;
using Microsoft.AspNetCore.WebUtilities;
using Entities.Contracts;

namespace TechHelper.Controllers
{
	[Route("api/account")]
	[ApiController]
	public class AccountController : ControllerBase
	{
		private readonly UserManager<User> _userManager;
		private readonly IUserRegistrationService _userRegistrationService;
		private IAuthenticationService _authenticationService;
		private readonly IEmailSender _emailSender;

		public AccountController(UserManager<User> userManager,
			IUserRegistrationService userRegistrationService,
			IEmailSender emailSender,
			IAuthenticationService authenticationService)
		{
			_userManager = userManager;
			this._userRegistrationService = userRegistrationService;
			_emailSender = emailSender;
			_authenticationService = authenticationService;
		}

		[HttpPost("register")]
		public async Task<IActionResult> RegisterUsesr(
			[FromBody] UserForRegistrationDto userForRegistrationDto)
		{
			#region V1
			//if (userForRegistrationDto == null || !ModelState.IsValid)
			//	return BadRequest();

			//var user = new User
			//{
			//	UserName = userForRegistrationDto.Name,
			//	Email = userForRegistrationDto.Email,
			//	PhoneNumber = userForRegistrationDto.PhoneNumber,
			//	Address = userForRegistrationDto.HomeAddress
			//};

			//var result = await _userManager.CreateAsync(user, userForRegistrationDto.Password);
			//if (!result.Succeeded)
			//{
			//	var errors = result.Errors.Select(e => e.Description);
			//	return BadRequest(new ResponseDto { Errors = errors });
			//}

			//await _userManager.SetTwoFactorEnabledAsync(user, true); 
			//var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);

			//var param = new Dictionary<string, string>
			//{
			//	{"token", token},
			//	{"email", userForRegistrationDto.Email}
			//};

			//var callback = QueryHelpers.AddQueryString(userForRegistrationDto.ClientURI, param);
			//try
			//{
			//	await _emailSender.SendEmailAsync(user.Email.ToString(), "Email Confirmation token", callback);
			//}
			//catch (Exception ex)
			//{
			//}

			//await _userManager.AddToRoleAsync(user, userForRegistrationDto.Roles.ToString());

			//return StatusCode(201);
			#endregion
			#region V2

			if (!ModelState.IsValid)
				return BadRequest(new ApiResponse(false, ModelState));

			var response = await _userRegistrationService.RegisterNewUserAsync(userForRegistrationDto);

			if (response.Status)
			{
				return StatusCode(201, response);
			}
			else
			{
				return BadRequest(response);
			}
			#endregion
		}

		[HttpPost("login")]
		public async Task<IActionResult> Logion(
			[FromBody] UserForAuthenticationDto userForAuthentication)
		{
			var user = await _userManager.FindByEmailAsync(userForAuthentication.Email);

			if (user == null )
			{
				return Unauthorized(new AuthResponseDto
				{
					ErrorMessage = "Invalid Request"
				});
			}

			//if(!await _userManager.IsEmailConfirmedAsync(user))
			//	return Unauthorized(new AuthResponseDto
			//	{
			//		ErrorMessage = "Email is not confirmed"
			//	});

			if(await _userManager.IsLockedOutAsync(user))
				return Unauthorized(new AuthResponseDto
				{
					ErrorMessage = "This account is locked out"
				});

			if (!await _userManager.CheckPasswordAsync(user,
				userForAuthentication.Password))
			{
				await _userManager.AccessFailedAsync(user);

				if(await _userManager.IsLockedOutAsync(user))
				{
					var content = $"Your account is locked out."
						+ $"if you want to reset the password, you can use Forgot password link On the login page";

					await _emailSender.SendEmailAsync(user.Email, "Locked out account information" ,content );
				}

				return Unauthorized(new AuthResponseDto
				{
					ErrorMessage = "Invalid Authentication"
				});
			}

			if(await _userManager.GetTwoFactorEnabledAsync(user))
				return await GenerateOTPFor2StepVerification(user);

			var token = await _authenticationService.GetToken(user);

			user.RefreshToken = _authenticationService.GenerateRefreshToken();
			user.RefreshTokenExpiryTime = DateTime.Now.AddDays(7);
			await _userManager.UpdateAsync(user);


			await _userManager.ResetAccessFailedCountAsync(user);

			return Ok(new AuthResponseDto
			{
				IsAuthSuccessful = true,
				Token = token,
				RefreshToken = user.RefreshToken
			});
		}

		private async Task<IActionResult> GenerateOTPFor2StepVerification(User user)
		{
			var providers = await _userManager.GetValidTwoFactorProvidersAsync(user);
			if(!providers.Contains("Email"))
			{
				return Unauthorized(new AuthResponseDto
				{
					ErrorMessage = "Invalid 2-Step verification Provider"
				});
			}

			var token = await _userManager.GenerateTwoFactorTokenAsync(user, "Email");

			await _emailSender.SendEmailAsync(user.Email, "Authentication token", token);

			return Ok(new AuthResponseDto
			{
				Is2StepVerificationRequired = true,
				Provider = "Email"
			});
		}

		[HttpPost("forgotPassword")]
		public async Task<IActionResult> ForgotPassword(
			[FromBody] ForgotPasswordDto forgotPasswordDto)
		{
			var user = await _userManager.FindByEmailAsync(forgotPasswordDto.Email);
			if (user == null)
				return BadRequest("Invalid Request");

			var token = await _userManager.GeneratePasswordResetTokenAsync(user);

			var param = new Dictionary<string, string>
			{
				{"token", token},
				{"email", forgotPasswordDto.Email}
			};

			var callback = QueryHelpers.AddQueryString(forgotPasswordDto.ClientURI, param);
			await _emailSender.SendEmailAsync( user.Email.ToString() , "Reset Password Token", callback);

			return Ok();
		}


		[HttpPost("resetPassword")]
		public async Task<IActionResult> ResetPassword(
			[FromBody] ResetPasswordDto resetPasswordDto)
		{
			var errorResponse = new ResetPasswordResponseDto
			{
				Errors = new string[] { "Reset Password Failed" }
			};
			if (!ModelState.IsValid)
				return BadRequest(errorResponse);

			var user = await _userManager.FindByEmailAsync(resetPasswordDto.Email);
			if (user == null) return BadRequest(errorResponse);

			var resetPassResult = await _userManager.ResetPasswordAsync(user,
				resetPasswordDto.Token, resetPasswordDto.Password);

			if (!resetPassResult.Succeeded)
			{
				var errors = resetPassResult.Errors.Select(e => e.Description);
				return BadRequest(new ResetPasswordResponseDto { Errors = errors });
			}

			await _userManager.SetLockoutEndDateAsync(user, null);

			return Ok(new ResetPasswordResponseDto { IsResetPasswordSuccessful = true});
		}

		[HttpGet("emailconfirmation")]
		public async Task<IActionResult> EmailConfirmaation([FromQuery] string email,
			[FromQuery] string token)
		{
			var user = await _userManager.FindByEmailAsync(email);
			if (user == null) return BadRequest();

			var confimResult = await _userManager.ConfirmEmailAsync(user, token);
			if (!confimResult.Succeeded) return BadRequest();

			return Ok();
		}


		[HttpPost("TwoStepVerification")]
		public async Task<IActionResult> TwoStepVerification(
			[FromBody] TwoFactorVerificationDto twoFactorVerificationDto)
		{
			if(!ModelState.IsValid) return BadRequest(
				new AuthResponseDto
				{
					ErrorMessage = "Invalid Request"
				});


			var user = await _userManager.FindByEmailAsync(twoFactorVerificationDto.Email);
			if(user == null) return BadRequest(
				new AuthResponseDto
				{
					ErrorMessage = "Invalid Request"
				});

			var validVerification = await _userManager.VerifyTwoFactorTokenAsync(user, twoFactorVerificationDto.Provider, twoFactorVerificationDto.TwoFactorToken);
			if(!validVerification) return BadRequest(
				new AuthResponseDto
				{
					ErrorMessage = "Invalid Request"
				});

			var token = await _authenticationService.GetToken(user);
			user.RefreshToken = _authenticationService.GenerateRefreshToken();
			user.RefreshTokenExpiryTime = DateTime.Now.AddDays(7);
			await _userManager.UpdateAsync(user);
			await _userManager.ResetAccessFailedCountAsync(user);

			return Ok(new AuthResponseDto
			{
				IsAuthSuccessful = true,
				Token = token,
				RefreshToken = user.RefreshToken
			});
		}
	}
}
