using System.Net.Mime;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using OpenWarehouse.auth.api.Common.AuthTokenRepo;
using OpenWarehouse.auth.api.Common.CustomIdentifyManager;
using OpenWarehouse.auth.api.Common.RegisterTokenManager;
using OpenWarehouse.auth.lib.Model.Account;
using OpenWarehouse.auth.lib.Model.Auth;

namespace OpenWarehouse.auth.api.Controller;

[ApiController]
[Route("[action]")]
public class Auth(
    SignInManager<ApplicationUser> signInManager, UserManager<ApplicationUser> userManager,
    AuthTokenRepo tokenRepo, ApplicationDbContext applicationDbContext,
    RegisterTokenManager registerTokenManager,
    ILogger<Auth> logger) : ControllerBase
{

    [HttpPost]
    [Consumes(MediaTypeNames.Application.Json, MediaTypeNames.Application.Xml)]
    public async Task<ActionResult<LoginModel>> Login([FromBody] LoginRequestModel requestModel)
    {
        logger.LogInformation("Login request received for user: {Login}", requestModel.Login);
        if (requestModel.Pass == null || requestModel.Login == null)
        {
            logger.LogWarning("Invalid login request model.");
            return BadRequest("Invalid request model.");
        }

        var loginModel = new LoginModel();

        if (requestModel.Pass == null)
        {
            loginModel.AccountProblem = "Login and password are required.";
            logger.LogWarning("Password is missing in the login request for user: {Login}", requestModel.Login);
            return BadRequest(loginModel);
        }

        var applicationUser = await userManager.FindByNameAsync(requestModel.Login) 
                              ?? await userManager.FindByEmailAsync(requestModel.Login);
        if (applicationUser == null)
        {
            logger.LogWarning("User not found for login: {Login}", requestModel.Login);
            loginModel.Message = "Incorrect login or password.";
            return Unauthorized(loginModel);
        }

        loginModel.Login = applicationUser.UserName;
        loginModel.Email = applicationUser.Email;

        var claims = await userManager.GetClaimsAsync(applicationUser);

        if (claims.Any(claim => claim.Type.Equals(AccountType.Service.GetValue())))
        {
            logger.LogWarning("Service account attempted to login: {Login}", requestModel.Login);
            return Forbid("This is a service account.");
        }

        if (applicationUser.LockoutEnabled)
        {
            logger.LogWarning("Locked account attempted to login: {Login}", requestModel.Login);
            return Forbid("Account is locked.");
        }

        var signInResult = await signInManager.CheckPasswordSignInAsync(applicationUser, requestModel.Pass, false);

        if (signInResult.Succeeded)
        {
            // var expiration = DateTime.UtcNow.AddHours(1);

            var expiration = DateTime.UtcNow.AddMinutes(30);
            loginModel.Token = await tokenRepo.CreateJwtToken(applicationUser, expiration, null);
            loginModel.Exp = expiration.ToBinary();
            loginModel.IsLogged = true;
            logger.LogInformation("User logged in successfully: {Login}", requestModel.Login);
            return Ok(loginModel);
        }

        if (signInResult.RequiresTwoFactor)
        {
            logger.LogInformation("Two-factor authentication required for user: {Login}", requestModel.Login);
            loginModel.Message = "Two-factor authentication is required.";
            loginModel.RequiresTwoFactor = true;
            return Ok(loginModel);
        }
        
        logger.LogWarning("Incorrect login or password for user: {Login}", requestModel.Login);
        loginModel.Message = "Incorrect login or password.";
        return Unauthorized(loginModel);
    }
    
    [HttpPost]
    [Consumes(MediaTypeNames.Application.Json, otherContentTypes: [MediaTypeNames.Application.Xml])]
    public async Task<ActionResult<RegisterReturnModel>> Register([FromBody]RegisterModel registerModel)
    {
        logger.LogInformation("Registration request received for user: {Username}", registerModel.Username);
        var registerReturnModel = new RegisterReturnModel();
        registerReturnModel.IsCreated = true;
        if (registerModel.Username != null)
        {
            var user = new ApplicationUser(registerModel.Username);
            user.Email = registerModel.Email;

            if (registerModel.RegisterToken != null)
            {
                var regToken = await registerTokenManager.GetToken(registerModel.RegisterToken);

                if (regToken == null)
                {
                    logger.LogWarning("Registration token not found: {RegisterToken}", registerModel.RegisterToken);
                    registerReturnModel.IsCreated = false;
                    registerReturnModel.ProblemWithToken = "Token Not found;";
                    return Unauthorized(registerReturnModel);
                }
                
                if (regToken.IsUsed)
                {
                    logger.LogWarning("Registration token already used: {RegisterToken}", registerModel.RegisterToken);
                    registerReturnModel.IsCreated = false;
                    registerReturnModel.ProblemWithToken = "Token is alleredy used;";
                }

                if (( registerModel.Password ?? "" ).Length > 6)
                {
                    registerReturnModel.IsCreated = false;
                }

                if (!registerReturnModel.IsCreated) return Unauthorized(registerReturnModel);

                if (registerModel.Password != null)
                {
                    var result = await userManager.CreateAsync(user, registerModel.Password);
                    var resultClaim = await userManager.AddClaimAsync(user, AccountType.User.Claim());
                    if (result.Succeeded && resultClaim.Succeeded)
                    {
                        regToken.IsUsed = true;
                        await applicationDbContext.SaveChangesAsync();
                        registerReturnModel.AuthToken =
                            await tokenRepo.CreateJwtToken(user, DateTime.UtcNow.AddHours(1), null);
                        logger.LogInformation("User registered successfully: {Username}", registerModel.Username);
                    }
                    else
                    {
                        foreach (var error in result.Errors)
                        {
                            logger.LogError("Error during user registration: {Code} - {Description}", error.Code, error.Description);
                            registerReturnModel.OtherWithOtherProblem += error.Code + "-" + error.Description + ";";
                            return Unauthorized(registerReturnModel);
                        }
            
                    }
                }
            }
            else
            {
                logger.LogWarning("Registration token is required but missing for user: {Username}", registerModel.Username);
                return BadRequest("Token are required.");
            }
        }


        return Ok(registerReturnModel);
    }
    
    
    [HttpGet]
    [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
    public async Task<LogOutModel> LogOut()
    {
        logger.LogInformation("Logout request received.");
        if (Request.Headers.TryGetValue("Authorization", out var token))
        {
            var tokenStr = token.ToString();
            string?[] tokenSplit = tokenStr.Split(" ");
            if (tokenSplit.Length > 0) tokenStr = tokenSplit[1];
            if (await tokenRepo.LockTokenAsync(tokenStr) > 0)
            {
                logger.LogInformation("User logged out successfully.");
                return new LogOutModel(true, "Logout successful");
            }
        }
        logger.LogWarning("Logout failed due to missing or invalid token.");
        return new LogOutModel(false, "Logout Fail");
    }
    
    [HttpGet]
    [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
    public async Task<ActionResult<RegeneratedTokenModel>> RegenerateToken()
        {
            logger.LogInformation("Token regeneration request received.");
            Request.Headers.TryGetValue("Authorization", out var token);

            token = token.ToString().Split(" ")[1];
                
            var newTokenStr = await tokenRepo.RegenerateJwtToken(token, DateTime.UtcNow.AddMinutes(30));
                
            logger.LogInformation("Token regenerated successfully");

            var respond = new RegeneratedTokenModel()
            {
                Mess = newTokenStr != null ? "Regeneration success" : "Regeneration fail",
                Token = newTokenStr
            };
            
            return Ok(respond);
        }

    [HttpDelete]
    [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
    [HasPrivilege(GlobalPrivilage.Privilege.GlobalDeleteExpiredToken)]
    public async Task<ActionResult<int>> DeleteExpiredToken()
    {
        logger.LogInformation("Delete expired tokens request received.");
        return Ok(await tokenRepo.DeleteInactiveToken());
    }

    [HttpPost]
    public async Task<ActionResult<bool>> TokenIsLock([FromBody] TokenAskIfIsLockModel req)
    {
        logger.LogInformation("Check if token is locked request received.");
        return Ok(await tokenRepo.IsTokenLockAsync(req.Token));
    }
    
}