using Microsoft.AspNetCore.Mvc;
using Auth.Services.DatabaseService;
using System.Web.Http;
using Auth.Entities;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Auth.Services;
using Auth.DTO;
using System.Security.Cryptography;
using System.Text;

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

        public MAuthController(DatabaseService db) : base(db) { }

        // Sends the public key to clients so they can verify sessions. - Verified working
        [HttpGet("publickey")]
        public IActionResult PublicKey() {
            try {
                RSA rsa = AuthJWT.RsaPublicKey.Rsa;
                byte[] publicKey = rsa.ExportSubjectPublicKeyInfo();
                string base64 = Convert.ToBase64String(publicKey);
                StringBuilder sb = new StringBuilder();
                sb.AppendLine("-----BEGIN PUBLIC KEY-----");
                for (int i = 0; i < base64.Length; i += 64) {
                    sb.AppendLine(base64.Substring(i, Math.Min(64, base64.Length - i)));
                }
                sb.AppendLine("-----END PUBLIC KEY-----");
                return Ok(sb.ToString());
            } catch (SecurityTokenException ex) {
                return BadRequest("Token invalid: " + ex.Message);
            }
        }

        // Login and return a ticket to retreive your JWT - Verified working
        [HttpPost("login")]
        public async Task<ActionResult> Authenticate([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);
                            
                            if (request.SameSite) {
                                SignIn(Response, AuthJWT.GenereateJWTToken(test, request.StayLoggedIn));
                            } else {
                                string Ticket = Guid.NewGuid().ToString().Replace("-", "");
                                string JWT = AuthJWT.GenereateJWTToken(test, request.StayLoggedIn);
                                AuthJWT.LoginSessions[Ticket] = new JWTMemCache {
                                    JWT = JWT,
                                    ExpiresAt = DateTime.UtcNow.AddSeconds(30)
                                };

                                return Ok(Ticket);
                            }
                        } else {
                            test.CurrentPasswordAttempts += 1;
                            await _databaseService.SetAccount(test);
                            return BadRequest("Wrong Password");
                        }
                    } else {
                        return BadRequest("You need to verify your email before you can sign in");
                    }
                }
                return BadRequest("Account Not Found");
            } catch (Exception ex) {
                Console.WriteLine("Login Error: " + ex.Message);
                return BadRequest("An internal server error has occured");
            }
        }

        // Use your ticket to get the JWT - Verified working
        [HttpPost("token")]
        public ActionResult Token([FromBody] JWTRequest request) {
            try {
                if (AuthJWT.LoginSessions.ContainsKey(request.Ticket)) {
                    JWTMemCache JWTObj = AuthJWT.LoginSessions[request.Ticket];
                    if (JWTObj.ExpiresAt >= DateTime.UtcNow) {
                        string JWT = JWTObj.JWT;
                        AuthJWT.LoginSessions.Remove(request.Ticket);
                        return Ok(JWT);
                    } else {
                        AuthJWT.LoginSessions.Remove(request.Ticket);
                        return BadRequest("The session ticket has already expired");
                    }
                }
                return BadRequest("The session ticket cannot be found");
            } catch (Exception ex) {
                Console.WriteLine("Token Error: " + ex.Message);
                return BadRequest("An internal server error has occured");
            }
        }

        // Renews an old JWT before it expires - Not Tested
        [HttpPost("renew")]
        public IActionResult Session([FromBody] JWTRenewRequest request) {
            try {
                JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
                ClaimsPrincipal claimsPrincipal = handler.ValidateToken(request.JWT, AuthJWT.TokenParameters, out var validatedToken);
                JwtSecurityToken jwt = (JwtSecurityToken)validatedToken;
                if (jwt != null) {
                    if (jwt.ValidTo - DateTime.UtcNow < TimeSpan.FromDays(1)) {
                        var newJwt = AuthJWT.RenewJWTToken(claimsPrincipal);
                        return Ok(newJwt);
                    }
                    return BadRequest("Not ready to renew");
                }
                return BadRequest("Malformed Token");
            } catch (SecurityTokenException ex) {
                return BadRequest("Token invalid: " + ex.Message);
            }
        }

    }
}
