﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using BrillionCore.Data;
using BrillionCore.Data.Model;
using BrillionCore.Data.Model.Account;
using BrillionCore.Services.Security;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace BrillionCore.UI.Account.Login
{
	public class LoginController : BaseAccountController
	{
		private ILogger Log { get; }
		private IQueryDispatcher QueryDispatcher { get; }


		public LoginController(ILogger<AccountController> log, IQueryDispatcher queryDispatcher)
		{
			Log = log;
			QueryDispatcher = queryDispatcher;
		}

		[HttpGet, Route("/Login", Name = "LoginGet"), AllowAnonymous]
		public IActionResult Login([FromQuery] string returnUrl = null)
		{
			return View(new LoginViewModel { ReturnUrl = returnUrl });
		}

		[HttpPost, Route("/Login", Name = "LoginPost"), AllowAnonymous, ValidateAntiForgeryToken]
		public async Task<IActionResult> Login([FromForm] LoginViewModel model, [FromServices] PasswordEncryptionServiceFactory passwordEncryptionServiceFactory)
		{
			ViewBag.ErrorMessage = null;
			if (!ModelState.IsValid)
			{
				Log.LogWarning("Login failed: Model not valid.");
				return HandleFailedLogin(model);
			}

			// Fetch Login Info
			var loginInfoQuery = new AccountLoginInformationQuery(model.EmailAddress);
			var loginInfo = QueryDispatcher.Dispatch<AccountLoginInformation>(loginInfoQuery);

			if (null == loginInfo)
			{
				Log.LogWarning($"Login failed: Login [{model.EmailAddress}] does not exist");
				return HandleFailedLogin(model);
			}

			// Validate Password
			var encryptionService = passwordEncryptionServiceFactory.GetServiceNamed(loginInfo.PasswordAlgorithm);
			var encryptedPassword = encryptionService.Encrypt(loginInfo.PasswordSalt, model.Password);

			if (!encryptedPassword.IsEqualTo(loginInfo.PasswordHash))
			{
				Log.LogWarning($"Login failed: Login [{model.EmailAddress}] password is not correct");
				return HandleFailedLogin(model);
			}


			// TODO: Query User Information and create Claims.
			var userAccountQuery = new FetchUserAccountByIdQuery(loginInfo.UserAccountId);
			var userAccount = QueryDispatcher.Dispatch<UserAccount>(userAccountQuery);
			const string Issuer = "https://contoso.com"; // TODO: Configuration?
			var claims = new List<Claim> { new Claim(ClaimTypes.Name, userAccount.DisplayName, ClaimValueTypes.String, Issuer) };
			foreach (var role in userAccount.AccountRoles.Where(r => r.Enabled))
			{
				claims.Add(new Claim(ClaimTypes.Role, role.Name, ClaimValueTypes.String, Issuer));
			}

			var userIdentity = new ClaimsIdentity("LoginForm");
			userIdentity.AddClaims(claims);

			var userPrincipal = new ClaimsPrincipal(userIdentity);


			// TODO: Event for Login
			Log.LogDebug($"Login successful: [{model.EmailAddress}]");

			await HttpContext.Authentication.SignInAsync("Cookie", userPrincipal,
				new AuthenticationProperties
				{
					ExpiresUtc = DateTime.UtcNow.AddMinutes(20),
					IsPersistent = false,
					AllowRefresh = false
				});

			return string.IsNullOrEmpty(model.ReturnUrl)
				? RedirectToAction("Index", "Home")
				: RedirectToLocal(model.ReturnUrl);
		}

		private IActionResult RedirectToLocal(string returnUrl)
		{
			return Url.IsLocalUrl(returnUrl)
				? (IActionResult)Redirect(returnUrl)
				: RedirectToAction("Index", "Home");
		}

		private IActionResult HandleFailedLogin(LoginViewModel postModel)
		{
			// TODO: Event for failed login
			Log.LogWarning("Failed Login Event");

			ViewBag.ErrorMessage = "Login Failed: Username or password is not correct.";
			return View(new LoginViewModel { EmailAddress = postModel.EmailAddress, ReturnUrl = postModel.ReturnUrl });
		}
	}
}
