using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using BoredCareers.Services;
using BoredCareers.Services.DatabaseService;
using BoredCareers.Entities;
using Microsoft.AspNetCore.Http.HttpResults;
using System.Web.Http;

namespace BoredCareers.Controllers {
    [ApiController]
    [Route("api/account/")]
    public class AuthenticationController : MistoxControllerBase {

        EmailService _emailContext;

        public AuthenticationController(DatabaseService db, EmailService emailContext) : base(db) {
            _emailContext = emailContext;
        }

        [Route("login")]
        [HttpPost]
        public async Task<ActionResult<Account>> Login([FromForm] string UserName, [FromForm] string PasswordHash, [FromForm] bool StayLoggedIn) {
            try {
                Account? test = await _databaseService.GetAccount(UserName.ToLower());
                if (test != null) {
                    if (test.EmailVerified == true) {
                        if (test.FailedPasswordLock) {
                            if (test.CurrentPasswordAttempts >= test.PasswordAttempts) {
                                return NotFound("Too many failed password attempts. Please reset your password");
                            }
                        }
                        if (BCrypt.Net.BCrypt.Verify(PasswordHash, test.PasswordHash)) {
                            test.CurrentPasswordAttempts = 0;
                            await _databaseService.SetAccount(test);

                            List<Claim> claims = new List<Claim>() {
                                new Claim("ID", test.ID.ToString()),
                                new Claim(ClaimTypes.NameIdentifier, test.ID.ToString())
                            };

                            await HttpContext.SignInAsync(
                                CookieAuthenticationDefaults.AuthenticationScheme,
                                new ClaimsPrincipal(new ClaimsIdentity(claims, "Auth")),
                                new AuthenticationProperties {
                                    ExpiresUtc = DateTime.UtcNow.AddYears(30),  // Add 30 years with sliding on
                                    IsPersistent = StayLoggedIn,                // Is set from the StayLoggedIn
                                }
                            );
                            return test;
                        } else {
                            test.CurrentPasswordAttempts += 1;
                            await _databaseService.SetAccount(test);
                            return Ok(new Account() { ID = -1, UserName = "Wrong Password" });
                        }
                    } else {
                        await SendVerify(test.UserName);
                        return NotFound("A new verify email has been sent. \n Note only 1 email send every 5 mintes");
                    }
                }
                return NotFound("Account Not Found");
            } catch (Exception ex) {
                Console.WriteLine("Login Error: " + ex.Message);
                return NotFound();
            }
        }

        [Route("register")]
        [HttpPost]
        public async Task<ActionResult<Account>> Register([FromForm] string Email, [FromForm] string UserName, [FromForm] string PasswordHash) {
            try {
                if (await _databaseService.GetAccount(UserName.ToLower()) == null) {
                    if (await _databaseService.GetAccount(Email.ToLower()) == null) {
                        Account created = new Account() {
                            UserName = UserName.ToLower(),
                            Email = Email.ToLower(),
                            EmailVerified = false,
                            PasswordHash = BCrypt.Net.BCrypt.HashPassword(PasswordHash),
                        };
                        await _databaseService.SetAccount(created);
                        Account? loadedAccount = await _databaseService.GetAccount(Email.ToLower());
                        if (loadedAccount != null) {
                            await SendVerify(loadedAccount.UserName);
                            return Ok(loadedAccount);
                        }
                        return NotFound("Unable to create the account");
                    } else {
                        return NotFound("Email is already in use");
                    }
                } else {
                    return NotFound("UserName is taken");
                }
            } catch (Exception ex) {
                Console.WriteLine("Register Error: " + ex.Message);
                return NotFound();
            }

        }

        [Route("changepassword")]
        [HttpPost]
        public async Task<ActionResult> ChangePassword([FromForm] string OldPassword, [FromForm] string NewPassword) {
            try {
                if (isLoggedIn()) {
                    Account user = await getLoggedInUser();
                    if (BCrypt.Net.BCrypt.Verify(OldPassword, user.PasswordHash)) {
                        user.PasswordHash = BCrypt.Net.BCrypt.HashPassword(NewPassword);
                        user.CurrentPasswordAttempts = 0;
                        await _databaseService.SetAccount(user);
                        return Ok();
                    }
                }
                return NotFound();
            } catch (Exception ex) {
                Console.WriteLine("ChangePassword Error: " + ex.Message);
                return NotFound();
            }
        }

        [Route("toggleaccountlock")]
        [HttpPost]
        public async Task<ActionResult<string>> ToggleAccountLock([FromForm] bool AccountLock) {
            try {
                if (isLoggedIn()) {
                    Account user = await getLoggedInUser();
                    user.FailedPasswordLock = AccountLock;
                    user.CurrentPasswordAttempts = 0;
                    await _databaseService.SetAccount(user);
                    return Ok();
                }
                return NotFound();
            } catch (Exception ex) {
                Console.WriteLine("ToggleAccountLock Error: " + ex.Message);
                return NotFound();
            }
        }

        [Route("get")]
        [HttpPost]
        public async Task<ActionResult<Account>> Get() {
            try {
                if (isLoggedIn()) {
                    return Ok(await getLoggedInUser());
                }
                return NotFound();
            } catch (Exception ex) {
                Console.WriteLine("Get Error: " + ex);
                return NotFound();
            }
        }

        [Route("logout")]
        [HttpPost]
        public async Task<ActionResult> Logout() {
            if (isLoggedIn()) {
                await HttpContext.SignOutAsync();
                return Ok();
            }
            return NotFound();
        }

        [Route("sendverifyemail")]
        [HttpPost]
        public async Task<ActionResult<string>> SendVerify([FromForm] string UserName) {
            try {
                string key = "v" + UserName;
                // Stop from sending multiple emails quickly
                if (_emailContext._SentEmails.ContainsKey(key)) {
                    DateTime PreviousSentTime = _emailContext._SentEmails.GetValueOrDefault(key);
                    if (PreviousSentTime.AddMinutes(5) > DateTime.Now) {
                        return "Cannot sent another verify email until 5 minutes has elapsed ";
                    }
                    else {
                        _emailContext._SentEmails.Remove(key);
                    }
                }
                Account? test = await _databaseService.GetAccount(UserName.ToLower());
                if (test != null) {
                    test.EmailToken = Guid.NewGuid().ToString();
                    await _databaseService.SetAccount(test);

                    string EmailContents = EmailService.VerifyEmailEmail;
                    EmailContents = Substitue(EmailContents, "@UserName", UserName);
                    EmailContents = Substitue(EmailContents, "@UserName", UserName);
                    EmailContents = Substitue(EmailContents, "@VerifyPassword", test.EmailToken);

                    string result = _emailContext.Send(test.Email, EmailService.VerifyEmailSubject, EmailContents);
                    _emailContext._SentEmails.Add(key, DateTime.Now);
                    return result;
                }
                return "Account not found";
            } catch (Exception) {
                return "The connection couldn't be established to the email server";
            }
        }

        [Route("verifyemail")]
        [HttpPost]
        public async Task<ActionResult<bool>> VerifyEmail([FromForm] string UserName, [FromForm] string EmailToken) {
            try {
                Account? test = await _databaseService.GetAccount(UserName.ToLower());
                if (test != null) {
                    if (!string.IsNullOrEmpty(test.EmailToken) && test.EmailToken == EmailToken) {
                        test.EmailToken = "";
                        test.EmailVerified = true;
                        await _databaseService.SetAccount(test);
                        return true;
                    }
                }
                return false;
            } catch {
                return false;
            }
        }

        [Route("sendresetpassword")]
        [HttpPost]
        public async Task<ActionResult<string>> ResetPassword([FromForm] string Email) {
            try {
                string key = "p" + Email.ToLower();
                // Stop from sending multiple emails quickly
                if (_emailContext._SentEmails.ContainsKey(key)) {
                    DateTime PreviousSentTime = _emailContext._SentEmails.GetValueOrDefault(key);
                    if (PreviousSentTime.AddMinutes(5) > DateTime.Now) {
                        return "Cannot sent another reset requests until 5 minutes has elapsed";
                    }
                    else {
                        _emailContext._SentEmails.Remove(key);
                    }
                }
                Account? test = await _databaseService.GetAccount(Email.ToLower());
                if (test != null) {
                    test.EmailToken = Guid.NewGuid().ToString();
                    await _databaseService.SetAccount(test);

                    string EmailContents = EmailService.ResetPasswordEmail;
                    EmailContents = Substitue(EmailContents, "@UserName", test.UserName);
                    EmailContents = Substitue(EmailContents, "@UserName", test.UserName);
                    EmailContents = Substitue(EmailContents, "@ResetPassWord", test.EmailToken);

                    string result = _emailContext.Send(test.Email, EmailService.VerifyEmailSubject, EmailContents);
                    _emailContext._SentEmails.Add(key, DateTime.Now);
                    return result;
                }
                return "Account Not Found";
            } catch (Exception e) {
                Console.WriteLine("EmailService Error: " + e.ToString());
                return "The connection couldn't be established to the email server";
            }

        }

        [Route("resetpassword")]
        [HttpPost]
        public async Task<ActionResult<bool>> ResetPwdVerify([FromForm] string UserName, [FromForm] string NewPassword, [FromForm] string ResetToken) {
            try {
                Account? test = await _databaseService.GetAccount(UserName.ToLower());
                if (test != null && !string.IsNullOrEmpty(test.EmailToken)) {
                    if (!string.IsNullOrEmpty(test.EmailToken) && test.EmailToken == ResetToken) {
                        test.CurrentPasswordAttempts = 0;
                        test.EmailToken = "";
                        test.PasswordHash = BCrypt.Net.BCrypt.HashPassword(NewPassword);
                        await _databaseService.SetAccount(test);
                        return true;
                    }
                }
                return false;
            } catch {
                return false;
            }
        }

        [Route("delete")]
        [HttpPost]
        public async Task<ActionResult> delete([FromForm] string Password) {
            try {
                if (isLoggedIn()) {
                    Account user = await getLoggedInUser();
                    if (BCrypt.Net.BCrypt.Verify(Password, user.PasswordHash)) {
                        await _databaseService.DeleteAccount(user.ID);
                        return Ok();
                    }
                }
                return NotFound();
            } catch (Exception ex) {
                Console.WriteLine("Delete Error: " + ex.Message);
                return NotFound();
            }
        }

    }
}
