using Microsoft.AspNetCore.Mvc;
using Auth.Services;
using Auth.Services.DatabaseService;
using Auth.Entities;
using Auth.DTO;
using System.Web.Http;

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

        EmailService _emailContext;

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

        [Route("get")]
        [HttpPost]
        public async Task<ActionResult<Account>> Get() {
            try {
                if (isLoggedIn()) {
                    return Ok(await getLoggedInUser());
                }
                return BadRequest("Not logged in");
            } catch (Exception ex) {
                Console.WriteLine("Get Error: " + ex);
                return BadRequest("An internal server error has occured");
            }
        }

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

                            string jwt = AuthJWT.GenereateJWTToken(test.ID, request.StayLoggedIn);
                            AuthJWT.SignIn(Response, request.StayLoggedIn, jwt);

                            return Ok(test);
                        } else {
                            test.CurrentPasswordAttempts += 1;
                            await _databaseService.SetAccount(test);
                            return BadRequest("Wrong Password");
                        }
                    } else {
                        await SendVerify(new SendVerifyEmailRequest {
                            UserName = test.UserName
                        });
                        return BadRequest("A new verify email has been sent. \n Note only 1 email send every 5 mintes");
                    }
                }
                return BadRequest("Account Not Found");
            } catch (Exception ex) {
                Console.WriteLine("Login Error: " + ex.Message);
                return BadRequest("An internal server error has occured");
            }
        }

        [Route("logout")]
        [HttpPost]
        public ActionResult Logout() {
            if (isLoggedIn()) {
                AuthJWT.SignOut(Response);
                return Ok();
            }
            return BadRequest();
        }

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

        }

        [Route("changepassword")]
        [HttpPost]
        public async Task<ActionResult> ChangePassword([FromBody] ChangePasswordRequest request) {
            try {
                if (isLoggedIn()) {
                    Account user = await getLoggedInUser();
                    if (BCrypt.Net.BCrypt.Verify(request.OldPassword, user.PasswordHash)) {
                        user.PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.NewPassword);
                        user.CurrentPasswordAttempts = 0;
                        await _databaseService.SetAccount(user);
                        return Ok();
                    }
                }
                return BadRequest("Not logged in");
            } catch (Exception ex) {
                Console.WriteLine("ChangePassword Error: " + ex.Message);
                return BadRequest("An internal server error has occured");
            }
        }

        [Route("accountlock")]
        [HttpPost]
        public async Task<ActionResult<string>> setAccountLock([FromBody] AccountLockRequest request) {
            try {
                if (isLoggedIn()) {
                    Account user = await getLoggedInUser();
                    user.FailedPasswordLock = request.AccountLock;
                    user.CurrentPasswordAttempts = 0;
                    await _databaseService.SetAccount(user);
                    return Ok();
                }
                return BadRequest("Not logged in");
            } catch (Exception ex) {
                Console.WriteLine("ToggleAccountLock Error: " + ex.Message);
                return BadRequest("An internal server error has occured");
            }
        }

        [Route("sendverifyemail")]
        [HttpPost]
        public async Task<ActionResult<string>> SendVerify([FromBody] SendVerifyEmailRequest request) {
            try {
                string key = "v" + request.UserName.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 BadRequest("Cannot sent another verify email until 5 minutes has elapsed");
                    } else {
                        _emailContext._SentEmails.Remove(key);
                    }
                }
                Account? test = await _databaseService.GetAccount(request.UserName.ToLower());
                if (test != null) {
                    test.EmailToken = Guid.NewGuid().ToString();
                    test.EmailTokenCreated = DateTime.UtcNow;
                    await _databaseService.SetAccount(test);

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

                    string result = _emailContext.Send(test.Email, EmailService.VerifyEmailSubject, EmailContents);
                    _emailContext._SentEmails.Add(key, DateTime.Now);
                    return Ok(result);
                }
                return BadRequest("Account not found");
            } catch (Exception) {
                return BadRequest("An internal server error has occured");
            }
        }

        [Route("verifyemail")]
        [HttpPost]
        public async Task<ActionResult<bool>> VerifyEmail([FromBody] VerifyEmailRequest request) {
            try {
                Account? test = await _databaseService.GetAccount(request.UserName.ToLower());
                if (test != null) {
                    if (DateTime.UtcNow < test.EmailTokenCreated.AddMinutes(30)) {
                        if (test.EmailToken == request.EmailToken) {
                            test.EmailToken = "";
                            test.EmailVerified = true;
                            await _databaseService.SetAccount(test);
                            return Ok(true);
                        }
                        return BadRequest("The token isn't valid");
                    }
                    return BadRequest("Your email token has timed out");
                }
                return BadRequest("Account not found");;
            } catch {
                return BadRequest("An internal server error has occured");
            }
        }

        [Route("sendresetpassword")]
        [HttpPost]
        public async Task<ActionResult<string>> ResetPassword([FromBody] SendResetPasswordRequest request) {
            try {
                string key = "p" + request.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 BadRequest("Cannot sent another reset requests until 5 minutes has elapsed");
                    } else {
                        _emailContext._SentEmails.Remove(key);
                    }
                }
                Account? test = await _databaseService.GetAccount(request.Email.ToLower());
                if (test != null) {
                    test.PasswordToken = Guid.NewGuid().ToString();
                    test.PasswordTokenCreated = DateTime.UtcNow;
                    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 Ok(result);
                }
                return BadRequest("Account Not Found");
            } catch (Exception e) {
                Console.WriteLine("EmailService Error: " + e.ToString());
                return BadRequest("An internal server error has occured");
            }

        }

        [Route("resetpassword")]
        [HttpPost]
        public async Task<ActionResult<bool>> ResetPwdVerify([FromBody] ResetPasswordRequest request) {
            try {
                Account? test = await _databaseService.GetAccount(request.UserName.ToLower());
                if (test != null) {
                    if (DateTime.UtcNow < test.PasswordTokenCreated.AddMinutes(30)) {
                        if (test.PasswordToken == request.PasswordToken) {
                            test.CurrentPasswordAttempts = 0;
                            test.PasswordToken = "";
                            test.PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.NewPassword);
                            await _databaseService.SetAccount(test);
                            return Ok(true);
                        }
                        return BadRequest("The token isn't valid");
                    }
                    return BadRequest("Your email token has timed out");
                }
                return BadRequest("Account not found");;
            } catch {
                return BadRequest("An internal server error has occured");
            }
        }

        [Route("delete")]
        [HttpPost]
        public async Task<ActionResult> delete([FromBody] DeleteRequest request) {
            try {
                if (isLoggedIn()) {
                    Account user = await getLoggedInUser();
                    if (BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash)) {
                        await _databaseService.DeleteAccount(user.ID);
                        return Ok();
                    }
                }
                return BadRequest("User is not logged in");
            } catch (Exception ex) {
                Console.WriteLine("Delete Error: " + ex.Message);
                return BadRequest("An internal server error has occured");
            }
        }

    }
}
