using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
using Kafka.Lib.Auth;
using MassTransit;
using Microsoft.IdentityModel.Tokens;
using OpenWarehouse.auth.api.Common.CustomIdentifyManager;
using OpenWarehouse.auth.api.Common.Exemptions;

namespace OpenWarehouse.auth.api.Common.AuthTokenRepo;

public class AuthTokenRepo(
    UserManager<ApplicationUser> userManager,
    ApplicationDbContext context,
    ILogger<AuthTokenRepo> logger,
    ITopicProducer<LogOutMessageType> logOutTopic,
    ITopicProducer<TokenValidationMessageType> tokenValidationTopic,
    IConfiguration configuration
    ) : IAuthTokenRepo
{
    public async Task<bool> IsTokenLockAsync(string? token)
    {
        if (token.Contains("Bearer")) token = token.Split(" ")[1];
        
        
        if (token == null) return true;
        
        var tokenObj = ParseToken(token);
        
        logger.LogInformation("Checking if token with GUID: {guid}, and user id {id} is locked", tokenObj?.Id, tokenObj?.Subject);   
        
        ApplicationUserTokenRepository? tokenM = await context.ApplicationUserTokenRepositories
            .Include(q => q.Creator)
            .Where(q => tokenObj != null && q.GuidToken != null && q.GuidToken.Equals(tokenObj.Id)).FirstOrDefaultAsync();
        
        if (tokenM?.IsLocked ?? true)
        {
            var tokenTopic = new TokenValidationMessageType()
            {
                Id = tokenM?.Id.ToString(),
                Mess = "Token is lock",
                Token = token
            };

            _ = tokenValidationTopic.Produce(tokenTopic);
            
            logger.LogDebug(tokenTopic.Text);
            logger.LogWarning("Token is locked: {Token}", token);
            return true;
        }
        
        logger.LogInformation("Token with GUID: {guid}, and user id {id} is unlocked", tokenObj?.Id, tokenObj?.Subject);
        
        return tokenM?.IsLocked ?? true;
    }

    public bool IsTokenLock(string? token)
    {
        var tokenObj = ParseToken(token);
        
        logger.LogInformation("Checking if token with GUID: {guid}, and user id {id} is locked", tokenObj?.Id, tokenObj?.Subject);
        
        
        ApplicationUserTokenRepository? tokenM = context.ApplicationUserTokenRepositories
            .Include(q=>q.Creator)
            .FirstOrDefault(q => q.GuidToken != null && q.GuidToken.Equals(token));
        
        
        if (tokenM?.IsLocked ?? true)
        {
            var tokenTopic = new TokenValidationMessageType()
            {
                Id = tokenM?.Id.ToString(),
                Mess = "Token is lock",
                Token = token
            };

            tokenValidationTopic.Produce(tokenTopic);
            logger.LogDebug(tokenTopic.Text);
            logger.LogWarning("Token is locked: {Token}", token);
            return true;
        }
        logger.LogInformation("Token with GUID: {guid}, and user id {id} is unlocked", tokenObj?.Id, tokenObj?.Subject);
        return false;
    }

    public async Task<int> LockTokenAsync(string? token)
    {
        var tokenObj = ParseToken(token);
        
        logger.LogInformation("Running LockTokenAsync function for GUID: {Guid}, User ID: {UserId}", tokenObj?.Id, tokenObj?.Subject);
        
        ApplicationUserTokenRepository? tokenM = await context.ApplicationUserTokenRepositories
            .Where(q => tokenObj != null && q.GuidToken != null && q.GuidToken.Equals(tokenObj.Id))
            .Include(q => q.Creator).FirstOrDefaultAsync();
        
        if (tokenM != null)
        {
            if (tokenM.IsLocked)
            {
                logger.LogWarning("Token is already locked for GUID: {Guid}, User ID: {UserId}", tokenObj?.Id, tokenObj?.Subject);
                return 0;
            }
            tokenM.IsLocked = true;
            if (tokenM.Creator == null) throw new AccountExemptions(false, "Account not found!");

            var topic = new LogOutMessageType()
            {
                Id = tokenM.Id.ToString(),
                Tokens = [token],
                UserName = tokenM.Creator.UserName
            };

            _ = logOutTopic.Produce(topic);
            logger.LogDebug("Produced logout topic for token: {Token}", topic.Text);
            logger.LogDebug("Token locked and logout topic produced for GUID: {Guid}, User ID: {UserId}", tokenObj?.Id, tokenObj?.Subject);
            
            
            return await context.SaveChangesAsync();
        }

        return 0;
    }

    public async Task<int> LockAllUserTokenAsync(ApplicationUser user)
    {
        logger.LogInformation("Locking all tokens for user: {UserId}", user.Id);
        
        var topic = new LogOutMessageType()
        {
            Id = user.Id,
            UserName = user.UserName
        };
        int count = 0;
        foreach (var tokenRepository in context.ApplicationUserTokenRepositories.Where(repository =>
                     repository.Creator == user && !repository.IsLocked))
        {
            if (tokenRepository.GuidToken != null) topic.Tokens?.Add(tokenRepository.GuidToken);
            tokenRepository.IsLocked = true;
            count++;

        }
        logger.LogDebug("Produced logout topic for user: {UserId}, Tokens: {Tokens}", user.Id, topic.Tokens);
        _ = logOutTopic.Produce(topic);
        await context.SaveChangesAsync();
        logger.LogInformation("Locked {Count} tokens for user: {UserId}", count, user.Id);
        return count;
    }

    public int LockToken(string token)
    {
        logger.LogInformation("Locking token: {Token}", token);
        ApplicationUserTokenRepository? tokenM = 
            context.ApplicationUserTokenRepositories.FirstOrDefault(q => q.GuidToken != null && q.GuidToken.Equals(token));
        if (tokenM != null)
        {
            tokenM.IsLocked = true;
            logger.LogInformation("Token locked successfully: {Token}", token);
            return context.SaveChanges();
        }
        
        logger.LogWarning("Token not found for locking: {Token}", token);
        return 0;
    }
    
    public async Task<int> AddTokenToRepAsync(string guid, string creator, DateTime exp, DateTime? created = null)
    {
        ApplicationUser? user = await userManager.FindByEmailAsync(creator) ??
                                await userManager.FindByNameAsync(creator);
        
        logger.LogInformation("Adding token to repository for creator: {Creator}", creator);
        if (user != null)
        {
            return await AddTokenToRepAsync(guid, user, exp,created);
        }
        
        logger.LogWarning("User not found for token creation: {Creator}", creator);

        return 0;

    }


    public async Task<int> AddTokenToRepAsync(string guid, ApplicationUser? creator, DateTime? exp, DateTime? created = null)
    {
        created ??= DateTime.UtcNow;
        if (creator == null) return 0;
        ApplicationUserTokenRepository newToken = new ApplicationUserTokenRepository(guid, creator, created, exp);
        context.ApplicationUserTokenRepositories.Add(newToken);

        return await context.SaveChangesAsync();
    }

    public async Task<int> DeleteAllUserTokenAsync(ApplicationUser user)
    {
        logger.LogInformation("Deleting all tokens for user: {UserId}", user.Id);
        var tokens = await context.ApplicationUserTokenRepositories
            .Where(q => q.Creator == user)
            .Select(q => q.GuidToken)
            .ToListAsync();
        
        var tokensInt = await context.ApplicationUserTokenRepositories
            .Where(q => q.Creator == user).ExecuteDeleteAsync();

        var topic = new LogOutMessageType()
        {
            UserName = user.UserName ?? "User not found",
            Id = user.Id,
            Tokens = tokens 
        };
        await logOutTopic.Produce(topic);
        logger.LogInformation("Deleted {Count} tokens for user: {UserId}", tokensInt, user.Id);
        return tokensInt;
    }

    public async Task<string?> GetLastUserToken(ApplicationUser? user)
    {
        logger.LogInformation("Retrieving last token for user: {UserId}", user?.Id);
        return await context.ApplicationUserTokenRepositories.Where(repository =>
            repository.Creator == user && repository.ExpireTime < DateTime.UtcNow).Select(repository => repository.GuidToken).FirstOrDefaultAsync();
    }

    public async Task<int> DeleteInactiveToken()
    {
        logger.LogInformation("Deleting inactive tokens");
        var deletedToken =
            await context.ApplicationUserTokenRepositories.Where(repository =>
                repository.ExpireTime < DateTime.UtcNow.AddHours(-2)).ExecuteDeleteAsync();
        logger.LogInformation("Deleted {Count} inactive tokens", deletedToken);
        return deletedToken;
    }

    public async Task<string?> CreateJwtToken(ApplicationUser user, DateTime? expires, List<Claim>? claims)
    {
        logger.LogInformation("Generating JWT token for user: {UserId}", user.Id);
        var tokenHandler = new JwtSecurityTokenHandler();

        var rsaSecurityKey = await GetSigningCredentials();
        
        var audience = configuration["Jwt:Audience"] ?? string.Empty;
        var issuer = configuration["Jwt:Issuer"] ?? string.Empty;

        if (user is { UserName: not null, Email: not null })
        {
            var claimList = await userManager.GetClaimsAsync(user);
            var claim = claimList.FirstOrDefault(claim1 => claim1.Type == AccountTypeFunction.AccountTypeType);
            var guid = Guid.NewGuid().ToString();
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new[]
                {
                    new Claim(JwtRegisteredClaimNames.Jti, guid),
                    new Claim(JwtRegisteredClaimNames.Sub, user.Id),
                    new Claim(ClaimTypes.NameIdentifier, user.Id),
                    new Claim(ClaimTypes.Name, user.UserName),
                    new Claim(ClaimTypes.Email, user.Email),
                    claim ?? AccountType.Uknow.Claim(),
                }),
                
                Expires = expires,
                Audience = audience,
                Issuer = issuer,
                SigningCredentials = rsaSecurityKey,
            };
            
            var token =  tokenHandler.CreateToken(tokenDescriptor);
            
            string? tokenString = tokenHandler.WriteToken(token);
            await AddTokenToRepAsync(guid, user, DateTime.UtcNow, tokenDescriptor.Expires);
            logger.LogInformation("JWT token generated successfully for user: {UserId}", user.Id);
            return  tokenString;
        }

        logger.LogWarning("Failed to generate JWT token due to missing user information.");
        return "";
    }

    public async Task<string?> RegenerateJwtToken(string? token, DateTime? expires)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        var tokenObj = tokenHandler.ReadJwtToken(token);
        
        var claims = tokenObj.Claims.ToList();
        
        claims.RemoveAll(c => c.Type == JwtRegisteredClaimNames.Jti ||
                              c.Type == JwtRegisteredClaimNames.Iat ||
                              c.Type == JwtRegisteredClaimNames.Nbf ||
                              c.Type == "unique_name" ||
                              c.Type == "email");
        
        var newJti = Guid.NewGuid().ToString();
        
        claims.Add(new Claim(JwtRegisteredClaimNames.Jti, newJti));
        
        var rsaSecurityKey = await GetSigningCredentials();
        
        var userId = claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Sub)?.Value;

        if (string.IsNullOrEmpty(userId))
        {
            logger.LogWarning("Cannot regenerate token: user ID not found in token claims.");
            return "";
        }
        
        var user = await userManager.FindByIdAsync(userId);
        
        if (user == null)
        {
            logger.LogWarning("Cannot regenerate token: user not found.");
            return "";
        }
        
        if (!string.IsNullOrEmpty(user.UserName) && !string.IsNullOrEmpty(user.Email))
        {
            claims.Add(new Claim(ClaimTypes.Name, user.UserName));
            claims.Add(new Claim(ClaimTypes.Email, user.Email));
        }
        else
        {
            return null;
        }

        await LockTokenAsync(token);
        
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(claims),
            Expires = expires,
            SigningCredentials = rsaSecurityKey,
        };
        
        var newToken =  tokenHandler.CreateToken(tokenDescriptor);
        
        string? tokenString = tokenHandler.WriteToken(newToken);
        await AddTokenToRepAsync(newJti, user, DateTime.UtcNow, tokenDescriptor.Expires);

        return tokenString;

    }

    public async Task<bool> TokenIsLock(string? token = null, string? guid = null)
    {
        
        if (guid == null && token != null)
        {
            guid = ParseToken(token).Id;
        }
        
        
        if (guid == null)
        {
            logger.LogWarning("Token fot GUID: {guid} is invalid.", guid);
            return true;
        }
        
        
        var result =  await context.ApplicationUserTokenRepositories
            .Where(repository => repository.GuidToken != null && repository.GuidToken.Equals(guid))
            .Select(repository => repository.IsLocked).FirstOrDefaultAsync();

        return result;
    }

    private  JwtSecurityToken ParseToken(string? token)
    {
        logger.LogTrace("Parse token");
        var tokenHandler = new JwtSecurityTokenHandler();
        var tokenObj = tokenHandler.ReadJwtToken(token);

        logger.LogDebug("Parsed token with GUID: {guid} and userID: {id}", tokenObj.Id, tokenObj.Subject);
        
        return tokenObj;
    }

    private async Task<SigningCredentials> GetSigningCredentials()
    {
        var contentRoot = configuration["contentRoot"] ?? throw new NullReferenceException();
        
        var privateKeyPath = Path.Combine(contentRoot,  "private_key_pkcs8.pem");

        var rsa = RSA.Create();
        
        rsa.ImportFromPem(await File.ReadAllTextAsync(privateKeyPath));

        var key = new RsaSecurityKey(rsa)
        {
            KeyId = "1"
        };
        
        return new SigningCredentials(key , SecurityAlgorithms.RsaSha256);
    }
}