using System;
using System.DirectoryServices;
using System.IdentityModel.Tokens.Jwt;
using System.Runtime.Versioning;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
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.Models;
using OnlineSalesAutoCrop.CoreAPI.Models.Global;
using OnlineSalesAutoCrop.CoreAPI.Models.Objects.Systems;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Systems;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Auth;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems;


namespace OnlineSalesAutoCrop.CoreAPI.Controllers
{
	/// <summary>
	/// 
	/// </summary>
	/// <remarks>
	/// 
	/// </remarks>
	/// <param name="service"></param>
	/// <param name="appSettings"></param>
	/// <param name="cache"></param>
	/// <param name="logger"></param>
	[Authorize]
	[ApiController]
	[ApiVersion("1.0")]
	[ValidateAntiForgeryToken]
	[Route("api/v{version:apiVersion}/IntegrationAuth")]

	public class IntegrationAuthController(IUserService service, IOptions<AppSettings> appSettings, IEaseCache cache, ILogger<IntegrationAuthController> 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();


		[HttpGet]
		[Route("GetServerDateTime")]
		[AllowAnonymous]
        [IgnoreAntiforgeryToken]
        public async Task<IActionResult> GetServerDateTime()
		{
			return Ok(DateTime.Now.ToString());
		}

		/// <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(IntegrationLoginResponse))]
		public async Task<IActionResult> Login([FromBody] IntegrationLoginRequest request)
		{
			ArgumentNullException.ThrowIfNull(request);

			IntegrationLoginResponse loginReponse = new();

			LoginResponse response = new();
			if (string.IsNullOrEmpty(request.LoginId))
			{
                loginReponse.ReturnStatus = StatusCodes.Status417ExpectationFailed;
                loginReponse.ReturnMessage.Add("Login ID is required.");
				return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse);
			}

			if (string.IsNullOrEmpty(request.Password))
			{
                loginReponse.ReturnStatus = StatusCodes.Status417ExpectationFailed;
                loginReponse.ReturnMessage.Add("Password is required.");
				return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse);
			}


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

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


				#endregion

				bool checkPwd = true;
				#region If AD (Active Directory) authentication is enabled do validate

				if (_appSettings.ADConfig.Enabled)
				{
					checkPwd = false;
					#pragma warning disable CA1416 // Validate platform compatibility
					int adLoginStatus = GetADLoginStatus(loginId: request.LoginId, password: request.Password);
					#pragma warning restore CA1416 // Validate platform compatibility
					if (adLoginStatus != 1)
					{
						response.LoginStatus = EnumLoginStatus.Unsuccessful;
						response.ReturnStatus = StatusCodes.Status403Forbidden;
						if (adLoginStatus == 2)
							response.ReturnMessage.Add("Active Directory User is DISABLED.");
						else if (adLoginStatus == 3)
							response.ReturnMessage.Add("Active Directory User's Password has EXPIRED.");
						else
							response.ReturnMessage.Add("Active Directory User/Password is INVALID.");

						return StatusCode(StatusCodes.Status417ExpectationFailed, response);
					}
				}

				#endregion

				ipAddress = Request.HttpContext.GetIpAddress();
				User user = await _service.IntegrationLoginAsync(request: request, ipAddress: ipAddress, checkPwd: checkPwd);

				if (user == null || user.UserId == 0)
				{
                    loginReponse.LoginStatus = EnumLoginStatus.Error;
                    loginReponse.ReturnStatus = StatusCodes.Status403Forbidden;
                    loginReponse.ReturnMessage.Add(checkPwd ? "Login ID/Password is invalid." : "You are not Authorized to login into the System");
					return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse);
				}

				if (user.LoginStatus == EnumLoginStatus.Unsuccessful)
				{
					string usm = string.Empty;
					if (user != null && !string.IsNullOrEmpty(user.UnsuccessfulMsg))
						usm = $" ({user.UnsuccessfulMsg})";

                    loginReponse.LoginStatus = user.LoginStatus;
                    loginReponse.ReturnStatus = StatusCodes.Status403Forbidden;
                    loginReponse.ReturnMessage.Add(checkPwd ? $"Login ID/Password is invalid{usm}." : "You are not Authorized to login into the System");
					return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse);
				}


				if (user.IsLocked)
				{
                    loginReponse.LoginStatus = user.LoginStatus;
                    loginReponse.ReturnStatus = StatusCodes.Status403Forbidden;
					if (!user.NextLoginTime.HasValue)
                        loginReponse.ReturnMessage.Add("You are locked, please contact Head office.");
					else
                        loginReponse.ReturnMessage.Add($"You can Login after {user.NextLoginTime:dd-MMM-yyyy H:mm:ss}");
					return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse);
				}

				if (user.Status != EnumStatus.Authorized)
				{
                    loginReponse.LoginStatus = user.LoginStatus;
                    loginReponse.ReturnStatus = StatusCodes.Status403Forbidden;
                    loginReponse.ReturnMessage.Add("You are not Authorized to Login into the System, Please contact with System Administrator.");
					return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse);
				}

				response.ValidUser = user.LoginStatus == EnumLoginStatus.Success;
				if (!response.ValidUser)
				{
                    loginReponse.LoginStatus = user.LoginStatus;
                    loginReponse.ReturnStatus = StatusCodes.Status403Forbidden;
                    loginReponse.ReturnMessage.Add("Unknown error.");
					return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse);
				}

				response.Map(user);
				response.ValidUser = true;
				response.ReturnStatus = StatusCodes.Status200OK;
				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", user.LoginId),
                        Helper.CreateClaim("Email", user.EmailAddress),
						Helper.CreateClaim("AuthKey", $"{user.AuthKey}"),
						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);
				GenerateRefreshTokenRequest refreshTokenRequest = new GenerateRefreshTokenRequest()
				{
					UserId = user.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 
				{
                    loginReponse.LoginStatus = user.LoginStatus;
                    loginReponse.ReturnStatus = StatusCodes.Status431RequestHeaderFieldsTooLarge;
                    loginReponse.ReturnMessage.Add("Authentication Token is too large for cookie.");
					return StatusCode(StatusCodes.Status431RequestHeaderFieldsTooLarge, loginReponse);
				}


				loginReponse.ReturnStatus = response.ReturnStatus;
				loginReponse.ReturnMessage = response.ReturnMessage;
				loginReponse.LoginId = response.LoginId;
				loginReponse.AccessToken = userToken;
				loginReponse.RefreshToken = refreshToken.RefreshToken;
                loginReponse.AccessTokenExpiry = refreshToken.ExpireTime;             
                return StatusCode(StatusCodes.Status200OK, loginReponse);

			}
			catch (Exception ex)
			{
				string msg = $"{request?.LoginId}~{ipAddress}";
				_logger.LogError(exception: ex, message: msg);
                loginReponse.ReturnStatus = StatusCodes.Status500InternalServerError;
                loginReponse.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
				return StatusCode(StatusCodes.Status500InternalServerError, loginReponse);
			}
		}


        /// <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(IntegrationLoginResponse))]
        public async Task<IActionResult> GetRefreshToken([FromBody] IntegrationRefreshTokenRequest request)
		{
			IntegrationRrefreshTokenResponse 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);
                    response.ReturnStatus = StatusCodes.Status401Unauthorized;
                    response.ReturnMessage.Add(msg);
                    return StatusCode(StatusCodes.Status401Unauthorized, response);
                }

                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("AuthKey", $"{userRefreshToken.AuthKey}"),
						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 StatusCode(StatusCodes.Status200OK, response);

            }
            catch (Exception ex)
            {
                string msg = $"{request?.RefreshToken}";
                _logger.LogError(exception: ex, message: msg);
                response.ReturnStatus = StatusCodes.Status500InternalServerError;
                response.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
                return StatusCode(StatusCodes.Status500InternalServerError, response);
            }
            return Ok();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="loginId"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        [SupportedOSPlatform("windows")]
        private int GetADLoginStatus(string loginId, string password)
        {
            try
            {
                const string displayNameAttribute = "DisplayName";
                const string samAccountNameAttribute = "SAMAccountName";
                const string userAccountControlAttribute = "useraccountcontrol";

                string username = (string.IsNullOrEmpty(_appSettings.ADConfig.Domain) || string.IsNullOrWhiteSpace(_appSettings.ADConfig.Domain)) ? loginId : $"{loginId}@{_appSettings.ADConfig.Domain}";
                using DirectoryEntry entry = new(path: _appSettings.ADConfig.Path, username: username, password: password);
                using DirectorySearcher searcher = new(searchRoot: entry);
                searcher.Filter = $"({samAccountNameAttribute}={loginId})";
                searcher.PropertiesToLoad.Add(value: displayNameAttribute);
                searcher.PropertiesToLoad.Add(value: samAccountNameAttribute);
                searcher.PropertiesToLoad.Add(value: userAccountControlAttribute);
                var result = searcher.FindOne();
                if (result == null)
                    return 0;

                ResultPropertyValueCollection displayName = result.Properties[name: displayNameAttribute];
                ResultPropertyValueCollection samAccountName = result.Properties[name: samAccountNameAttribute];
                ResultPropertyValueCollection userAccountControl = result.Properties[name: userAccountControlAttribute];
                int uacFlag = (userAccountControl != null && userAccountControl.Count > 0) ? Convert.ToInt32(userAccountControl[0]) : 0;
                if ((uacFlag & 0x000002) == 0x000002)       //Disabled
                    return 2;
                else if ((uacFlag & 0x800000) == 0x800000)  //Password expired
                    return 3;

                if (displayName != null && displayName.Count > 0 && samAccountName != null && samAccountName.Count > 0)
                    return 1;
                else
                    return 0;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex);
                return 0;
            }
        }
    }
}