using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using OnlineSalesAutoCrop.CoreAPI;
using OnlineSalesAutoCrop.CoreAPI.Models;
using OnlineSalesAutoCrop.CoreAPI.Models.Global;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.MobileApp;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.MobileApp;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Systems;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Auth;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace OnlineSalesAutoCrop.CoreAPI.Controllers
{
    /// <summary>
	/// 
	/// </summary>
	/// <remarks>
	/// 
	/// </remarks>
	/// <param name="service"></param>
	/// <param name="appSettings"></param>
	/// <param name="cache"></param>
	/// <param name="logger"></param>
	/// <param name="refreshTokenService"></param>
	[Authorize]
    [ApiController]
    [ApiVersion("1.0")]
    [ValidateAntiForgeryToken]
    [Route("api/mobile/v{version:apiVersion}/auth")]
    public class MobileAuthController(IUserService service, IOptions<AppSettings> appSettings, IEaseCache cache, ILogger<MobileAuthController> logger,
        IRefreshTokenService refreshTokenService) : ControllerBase
    {
        private readonly ILogger _logger = logger;
        private readonly IEaseCache _cache = cache;
        private readonly IUserService _service = service;
        private readonly IRefreshTokenService _refreshTokenService = refreshTokenService;
        private readonly AppSettings _appSettings = appSettings?.Value;
        private readonly DateTimeOffset _options = Helper.CreateEaseCacheOptions();


        /// <summary>
        /// Login using your credential data retrieve from SqlServer
        /// </summary>
        /// <remarks>
        /// </remarks>
        /// <param name="request"></param>
        /// <returns>If login successful ValidUser: true</returns>
        /// <response code="200">If login successful Return ValidUser: true and UserName: not empty</response>
        [HttpPost("Login")]
        [AllowAnonymous]
        [IgnoreAntiforgeryToken]
        [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(MobileAppAuthRequest))]
        public async Task<IActionResult> Login([FromBody] MobileAppAuthRequest request)
        {
            ArgumentNullException.ThrowIfNull(request);

            AppAuthUserResponse loginReponse = new();

            if (string.IsNullOrEmpty(request.LoginId))
            {
                string message = "Login ID is required.";
                return BadRequest(MobileResponseBase<AppAuthUserResponse>.Failure(message, StatusCodes.Status400BadRequest));
            }

            if (string.IsNullOrEmpty(request.Password))
            {
                string message = "Password is required.";
                return BadRequest(MobileResponseBase<AppAuthUserResponse>.Failure(message, StatusCodes.Status400BadRequest));
            }


            string ipAddress = string.Empty;
            try
            {
                #region Decrypt LoginID

                string cipherSecretKey = GlobalFunctions.ConvertFromBase64String(_appSettings.CipherSecretKey);


                #endregion

                bool checkPwd = true;

                ipAddress = Request.HttpContext.GetIpAddress();
                loginReponse = await _service.LoginAsync(request: request, ipAddress: ipAddress);

                if (loginReponse == null || loginReponse.UserId == 0)
                {
                    string message = "Login ID/Password is invalid.\" : \"You are not Authorized to login into the System.";
                    return Unauthorized(MobileResponseBase<AppAuthUserResponse>.Failure(message, StatusCodes.Status401Unauthorized));
                }
            
                if (loginReponse.LoginStatus == EnumLoginStatus.Unsuccessful)
                {
                    string message = checkPwd ? $"Login ID/Password is invalid for {request.LoginId}." : "You are not Authorized to login into the System";
                    return Unauthorized(MobileResponseBase<AppAuthUserResponse>.Failure(message, StatusCodes.Status401Unauthorized));
                }

                if (loginReponse.IsLocked)
                {
                    string message =$"Your Account is locked . Please try again after few minutes";
                    return Unauthorized(MobileResponseBase<AppAuthUserResponse>.Failure(message, StatusCodes.Status401Unauthorized));
                }

                if (!loginReponse.IsAuthorized )
                {
                    string message = $"You are not Authorized to Login into the System, Please contact with System Administrator.";
                    return Unauthorized(MobileResponseBase<AppAuthUserResponse>.Failure(message, StatusCodes.Status401Unauthorized));
                }

                string pwdSecretKey = GlobalFunctions.ConvertFromBase64String(_appSettings.PwdSecretKey);
                string userPwd = Ease.NetCore.Utility.Global.CipherFunctions.EncryptByAES(privateKey: pwdSecretKey, publicKey: pwdSecretKey, data: request.Password);
                byte[] key = Encoding.ASCII.GetBytes(_appSettings.JwtCryptoKey);
                JwtSecurityTokenHandler tokenHandler = new();
                var tokenDescriptor = new SecurityTokenDescriptor
                {
                    Subject = new ClaimsIdentity(
                    [
                        Helper.CreateClaim("LoginId", loginReponse.LoginId),
                        Helper.CreateClaim("Email", loginReponse.Email),
                        Helper.CreateClaim("UserId", $"{loginReponse.UserId}"),
                        Helper.CreateClaim("HashKey", Guid.NewGuid().ToString())
                    ]),
                    Expires = DateTime.UtcNow.AddMinutes(_appSettings.AccessTokenDuration),
                    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature)
                };

                SecurityToken token = tokenHandler.CreateToken(tokenDescriptor);
                string userToken = tokenHandler.WriteToken(token);
                GenerateRefreshTokenRequest refreshTokenRequest = new GenerateRefreshTokenRequest()
                {
                    UserId = loginReponse.UserId,
                    IpAddress = ipAddress,
                    RawRefreshToken = string.Empty
                };

                var refreshToken = await _refreshTokenService.GenerateRefreshTokenByUserAsync(refreshTokenRequest);

                //If token length is greater than or equal to 4096 (4KB) then return error
                //because cookie can not store more than 4KB data and we are storing this token in cookie for authentication
                if (userToken.Length >= 4096) //4Kb 
                {
                    string message = $"Authentication Token is too large for cookie.";
                    return StatusCode(StatusCodes.Status431RequestHeaderFieldsTooLarge, MobileResponseBase<AppAuthUserResponse>.Failure(message, StatusCodes.Status431RequestHeaderFieldsTooLarge));
                }

       
                loginReponse.LoginId = loginReponse.LoginId;
                loginReponse.AccessToken = userToken;
                loginReponse.RefreshToken = refreshToken.RefreshToken;
                loginReponse.AccessTokenExpiryTime = DateTime.UtcNow.AddHours(12);
                loginReponse.RefreshTokenExpiryTime = refreshToken.ExpireTime;

                return Ok(MobileResponseBase<AppAuthUserResponse>.Success(loginReponse));
            }
            catch (Exception ex)
            {
                string msg = $"{request?.LoginId}~{ipAddress}";
                _logger.LogError(exception: ex, message: msg);
                string mesgae= ex.InnerException != null ? ex.InnerException.Message : ex.Message;
                return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(mesgae, StatusCodes.Status500InternalServerError));
            }
        }


        /// <summary>
        /// Login using your credential data retrieve from SqlServer
        /// </summary>
        /// <remarks>
        /// </remarks>
        /// <param name="request"></param>
        /// <returns>If login successful ValidUser: true</returns>
        /// <response code="200">If login successful Return ValidUser: true and UserName: not empty</response>
        [HttpPost("RefreshToken")]
        [AllowAnonymous]
        [IgnoreAntiforgeryToken]
        [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(MobileRrefreshTokenResponse))]
        public async Task<IActionResult> GetRefreshToken([FromBody] IntegrationRefreshTokenRequest request)
        {
            MobileRrefreshTokenResponse response = new();
            try
            {
                var userRefreshToken = await _refreshTokenService.GetUserByRefreshTokenAsync(request.RefreshToken);
                if (!userRefreshToken.IsActive)
                {
                    string msg = $"Refresh Token is not active: {request?.RefreshToken}";
                    _logger.LogError(message: msg);
                    return Unauthorized( MobileResponseBase<AppAuthUserResponse>.Failure(msg, StatusCodes.Status401Unauthorized));
                }

                byte[] key = Encoding.ASCII.GetBytes(_appSettings.JwtCryptoKey);
                JwtSecurityTokenHandler tokenHandler = new();
                var tokenDescriptor = new SecurityTokenDescriptor
                {
                    Subject = new ClaimsIdentity(
                    [
                        Helper.CreateClaim("LoginId", userRefreshToken.LoginId),
                        Helper.CreateClaim("Email", userRefreshToken.EmailAddress),
                        Helper.CreateClaim("UserId", $"{userRefreshToken.UserId}"),
                        Helper.CreateClaim("HashKey", Guid.NewGuid().ToString())
                    ]),
                    Expires = DateTime.UtcNow.AddHours(12),
                    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature)
                };

                SecurityToken token = tokenHandler.CreateToken(tokenDescriptor);
                string userToken = tokenHandler.WriteToken(token);

                string ipAddress = Request.HttpContext.GetIpAddress();
                var refreshToken = await _refreshTokenService.GenerateRefreshTokenAsync(request.RefreshToken, ipAddress);

                response.AccessToken = userToken;
                response.RefreshToken = refreshToken;

                return Ok(MobileResponseBase<MobileRrefreshTokenResponse>.Success(response));
            }
            catch (Exception ex)
            {
                string msg = $"{request?.RefreshToken}";
                _logger.LogError(exception: ex, message: msg);
                return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError));
            }
        }


        /// <summary>
        /// Login using your credential data retrieve from SqlServer
        /// </summary>
        /// <remarks>
        /// </remarks>
        /// <param name="request"></param>
        /// <returns>If login successful ValidUser: true</returns>
        /// <response code="200">If login successful Return ValidUser: true and UserName: not empty</response>
        [HttpPost("Logout")]
        [Authorize]
        [IgnoreAntiforgeryToken]
        [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AuthLogoutResponse))]
        public async Task<IActionResult> Logout([FromBody] AuthLogoutRequest request)
        {
            AuthLogoutResponse response = new();
            try
            {
                string ipAddress = Request.HttpContext.GetIpAddress();
                response = await _service.LogoutAsync(request, ipAddress);
                return Ok(MobileResponseBase<AuthLogoutResponse>.Success(response, "Successfully Logout"));

            }
            catch (Exception ex)
            {
                string msg = $"Exception occur on logout {request?.LoginId}";
                _logger.LogError(exception: ex, message: msg);
                return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError));
            }
        }


        /// <summary>
        /// Login using your credential data retrieve from SqlServer
        /// </summary>
        /// <remarks>
        /// </remarks>
        /// <param name="request"></param>
        /// <returns>If login successful ValidUser: true</returns>
        /// <response code="200">If login successful Return ValidUser: true and UserName: not empty</response>
        [HttpPost("ChangePassword")]
        [Authorize]
        [IgnoreAntiforgeryToken]
        [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AppChangePasswordResponse))]
        public async Task<IActionResult> ChangePassword([FromBody] AppUserChangePasswordRequest request)
        {
            AppChangePasswordResponse response = new();
            try
            {
                if(request.Password.Equals(request.ConfirmPassword))
                {
                    string msg = $"You can't use your old password as your new password.";
                    _logger.LogError(message: msg);
                    return BadRequest(MobileResponseBase<AppAuthUserResponse>.Failure(msg, StatusCodes.Status400BadRequest));
                }

                string ipAddress = Request.HttpContext.GetIpAddress();
                response = await _service.ChangePasswordAsync(request, ipAddress);
                return Ok(MobileResponseBase<AppChangePasswordResponse>.Success(response, "Successfully Changed the Password"));

            }
            catch (Exception ex)
            {
                string msg = $"Exception occur on logout {request?.LoginId}";
                _logger.LogError(exception: ex, message: msg);
                return StatusCode(StatusCodes.Status500InternalServerError, MobileResponseBase<AppAuthUserResponse>.Failure(ex.InnerException != null ? ex.InnerException.Message : ex.Message, StatusCodes.Status500InternalServerError));
            }
        }
    }
}