using System.IdentityModel.Tokens.Jwt;
using System.Net.Mime;
using System.Text.Json;
using Kafka.Lib.Auth;
using Kafka.Lib.Auth.Model;
using MassTransit;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using OpenWarehouse.auth.api.Common.AuthTokenRepo;
using OpenWarehouse.auth.api.Common.CustomIdentifyManager;
using OpenWarehouse.auth.lib.Model;
using OpenWarehouse.auth.lib.Model.Account;

namespace OpenWarehouse.auth.api.Controller;

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[ApiController]
[Route("[controller]/[action]")]
public class Account(
    RoleManager<ApplicationRole> roleManager,
    PrivilegeManager privilegeManager,
    UserManager<ApplicationUser> userManager,
    AuthTokenRepo tokenRepo,
    ITopicProducer<UserChangeMessageType> userChangeProducer,
    ITopicProducer<UserChangeClaimsMessageType> userClaimChangeProducer,
    ILogger<Account> logger) : ControllerBase
{
    [HttpDelete]
    [HasPrivilege(GlobalPrivilage.Privilege.GlobalDeleteUser)]
    [Consumes(MediaTypeNames.Application.Json, otherContentTypes: [MediaTypeNames.Application.Xml])]
    public async Task<ActionResult<DeleteUserReturnModel>> DeleteUser([FromBody]DeleteUserRequestModel requestModel)
    {
        logger.LogInformation("DeleteUser called with UserId: {UserId}", requestModel.UserId);

        if (requestModel.UserId != null)
        {
            var user = await userManager.FindByIdAsync(requestModel.UserId);
            if (user != null)
            {
                logger.LogInformation("User found: {UserId}", user.Id);
                
                var tokenR = await tokenRepo.DeleteAllUserTokenAsync(user);
                var userR = await userManager.DeleteAsync(user);
                DeleteUserReturnModel returnModel = new DeleteUserReturnModel
                {
                    UserId = requestModel.UserId,
                    DedeltedTokenCount = tokenR,
                    IsOperationSuccess = userR.Succeeded,
                    OperationMessage = string.Join(";", userR.Errors)
                };

                var topic = new UserChangeMessageType()
                {
                    UserId = user.Id,
                    Changes = new Dictionary<string, string> { { "AccountChange", "Delete" } },
                    Id = HttpContext.TraceIdentifier,
                };
                
                _ = userChangeProducer.Produce(topic);
                
                logger.LogInformation("User deleted: {UserId}", user.Id);
                
                return Ok(returnModel);
            }
            else
            {
                logger.LogWarning("User not found: {UserId}", requestModel.UserId);
                return BadRequest("User not found");
            }
        }

        logger.LogWarning("DeleteUser request with empty UserId");
        return BadRequest("User cannot be empty");
    }

    [HttpPost]
    [HasPrivilege(GlobalPrivilage.Privilege.GlobalCreateUser)]
    [Consumes(MediaTypeNames.Application.Json, otherContentTypes: [MediaTypeNames.Application.Xml])]
    public async Task<ActionResult<CreateUserModelReturn>> CreateUser([FromBody] CreateUserModel newUser)
    {
        logger.LogInformation("CreateUser called with Username: {Username}, Email: {Email}", newUser.Username, newUser.Email);
        
        var userReturn = new CreateUserModelReturn()
        {
            Username = newUser.Username,
            Email = newUser.Email,
            Password = "*******",
            Roles = new List<CreateUserModelReturn.RoleStatus>(),
        };
        
        if (newUser is { Username: not null, Email: not null, Password: not null })
        {
            var user = new ApplicationUser {UserName  = newUser.Username, Email = newUser.Email,
                EmailConfirmed = true, DialCode = newUser.Dialcode, PhoneNumber = newUser.Phone};

            var userResult = await userManager.CreateAsync(user, newUser.Password);
            if (userResult.Succeeded)
            {
                logger.LogInformation("User created: {UserId}", user.Id);
                
                var resultClaim = await userManager.AddClaimAsync(user, AccountType.User.Claim());
                
                if (resultClaim.Succeeded && newUser.Roles != null)
                {
                    userReturn.IsCreated = true;
                    userReturn.NewUserId = user.Id;

                    foreach (var role in newUser.Roles)
                    {
                        var appRole = await roleManager.FindByNameAsync(role);

                        CreateUserModelReturn.RoleStatus roleStatus = new CreateUserModelReturn.RoleStatus();
                        var result = await userManager.AddToRoleAsync(user, role);

                        roleStatus.Added = result.Succeeded;
                        roleStatus.Name = role;
                        roleStatus.Exist = appRole != null;
                        roleStatus.Mess = JsonSerializer.Serialize(string.Join(";", result.Errors));

                        userReturn.Roles.Add(roleStatus);
                    }

                    var topicMess = new UserChangeMessageType
                    {
                        UserId = user.Id,
                        Id = user.Id,
                        Changes =
                        {
                            ["RoleChange"] = string
                                .Join(";", userReturn.Roles
                                    .Select(status => $"{status.Name}:{status.Added}/{status.Exist}")),
                            ["UserChange"] = "Created",
                            ["Success"] = $"{userReturn.IsCreated}"
                        }
                    };

                    await userChangeProducer.Produce(topicMess);
                    
                    logger.LogInformation("User created successfully: {UserId}", user.Id);
                    return Ok(userReturn);
                }
                else
                {
                    userReturn.ReturnMess = string.Join(';', userResult.Errors.Select(q => $"{q.Code}:{q.Description}"));
                    logger.LogWarning("Failed to add claims or roles to user: {UserId}", user.Id);
                    return StatusCode(406, userReturn);
                }
            }
            else
            {
                userReturn.ReturnMess = JsonSerializer.Serialize(string.Join(";", userResult.Errors.Select(q => $"{q.Code}:{q.Description}")));
                logger.LogWarning("Failed to create user: {Username}", newUser.Username);
                userReturn.IsCreated = false;
                return StatusCode(406, userReturn);
            }
        }
        else
        {
            logger.LogWarning("Invalid CreateUser request");
            userReturn.IsCreated = false;
            userReturn.ReturnMess = "Invalid request";
        }
        
        return StatusCode(406, userReturn);
    }

    [HttpPost]
    [Consumes(MediaTypeNames.Application.Json, otherContentTypes: [MediaTypeNames.Application.Xml])]
    public async Task<ActionResult<ChangePasswordReturn>> ChangePassword([FromBody] ChangePassword req)
    {
        logger.LogInformation("ChangePassword called for UserId: {UserId}", User.FindFirstValue(ClaimTypes.NameIdentifier));
        
        var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
        var user = await userManager.FindByIdAsync(userId ?? "");

        if ((user != null) && (req is { Password: not null, NewPassword1: not null, NewPassword2: not null }))
        {
            if (req.NewPassword1.Equals(req.NewPassword2))
            {
                var result = await userManager.ChangePasswordAsync(user, req.Password, req.NewPassword1);
                if (result.Succeeded)
                {
                    logger.LogInformation("Password changed successfully for UserId: {UserId}", user.Id);
                    var count = 0;
                    if (req.LogOutAllSession) count = await tokenRepo.LockAllUserTokenAsync(user);
                    return Ok(new ChangePasswordReturn { Mess = "Operation success.", IsSuccess = true, LockedTokenCount = count});
                }
                else
                {
                    logger.LogWarning("Failed to change password for UserId: {UserId}", user.Id);
                    return StatusCode(418, new ChangePasswordReturn {Mess = string.Join(";", result.Errors
                        .Select(q => q.Description) ), IsSuccess = false});
                }
            }
            else
            {
                logger.LogWarning("New passwords do not match for UserId: {UserId}", user.Id);
                return StatusCode(418, new ChangePasswordReturn {Mess = "New password not match.", IsSuccess = false});
            }
        }
        else
        {
            logger.LogWarning("Invalid ChangePassword request for UserId: {UserId}", userId);
            return StatusCode(400, new ChangePasswordReturn {Mess = "Bad request", IsSuccess = false});
        }
    }

    [HttpPost]
    [HasPrivilege(GlobalPrivilage.Privilege.GlobalDeactivateUserToken)]
    public async Task<ActionResult<OkModel>> DeactivateToken([FromBody] DeactiveTokenModel req)
    {
        logger.LogInformation("DeactivateToken called for Token: {Token}", req.Token);
        var i = await tokenRepo.LockTokenAsync(req.Token ?? throw new Exception("Token is null"));
        var resultMessage = i > 0 ? "Token lock success." : "Token Lock Fail.";
        logger.LogInformation(resultMessage);
        return new OkModel(resultMessage, i > 0);
    }

    [HttpPost]
    [HasPrivilege(GlobalPrivilage.Privilege.GlobalEditUser)]
    public async Task<ActionResult<AddClaimsToUserReturnModel>> AddClaimsToUser([FromBody] AddClaimsToUserModel req)
    {
        logger.LogInformation("AddClaimsToUser called for UserId: {UserId}", req.UserId);
        var userId = req.UserId;
        var user = await userManager.FindByIdAsync(userId) ?? throw new Exception("User not found");
        var topic = new UserChangeClaimsMessageType()
        {
            UserId = userId,
            Id = HttpContext.TraceIdentifier,
            ClaimChages = new List<ClaimChagesModel>()
        };

        var actionResult = new AddClaimsToUserReturnModel() { UserId = userId };
        
        var curClaims = await userManager.GetClaimsAsync(user);
        
        foreach (var newClaim in req.NewClaims)
        {
            var existingClaim = curClaims.FirstOrDefault(c => c.Type == newClaim.Key);

            topic.ClaimChages.Add(new ClaimChagesModel()
            {
                Old = existingClaim != null ?
                new ClaimModel
                {
                    ClaimType = existingClaim.Type,
                    ClaimValue = existingClaim.Value,
                    ClaimIssuer = existingClaim.Issuer
                } : null,
                New = new ClaimModel
                {
                    ClaimType = newClaim.Key,
                    ClaimValue = newClaim.Value,
                    ClaimIssuer = req.Issuer
                }
            });
        }

        var resultAddClaims = await userManager.AddClaimsAsync(user,
            req.NewClaims.Select(pair => new Claim(type: pair.Key, value: pair.Value, null, req.Issuer)).ToList());

        if (!resultAddClaims.Succeeded)
        {
            actionResult.IsSucess = false;
            actionResult.Error = string.Join(";", resultAddClaims.Errors.Select(error => error.Description));

            topic.IsSuccess = false;
            topic.Errors =
                resultAddClaims.Errors.Select(error => 
                    new KeyValuePair<string, string>(error.Code, error.Description))
                    .ToDictionary();

            logger.LogWarning("Failed to add claims to user: {UserId}", userId);
        }
        else
        {
            actionResult.IsSucess = true;
            topic.IsSuccess = true;
            logger.LogInformation("Claims added successfully to UserId: {UserId}", userId);
        }

        _ = userClaimChangeProducer.Produce(topic);
        
        var allUserClaim = await userManager.GetClaimsAsync(user);
        
        foreach (var claim in allUserClaim)
        {
            actionResult.AllUserClaims[claim.Type] = claim.Value;
        }

        return Ok(actionResult);
    }

    [HttpGet]
    public async Task<ActionResult<AddClaimsToUserReturnModel>> GetAllClaimsUser()
    {
        var userId = User.Claims.Where(q => q.Type == ClaimTypes.NameIdentifier)
            .Select(q => q.Value).FirstOrDefault();
        logger.LogInformation("GetAllClaimsUser called for UserId: {UserId}", userId);
        
        var user = await userManager.FindByIdAsync(userId ?? "") ?? throw new Exception("User not found");
        
        var result = new AddClaimsToUserReturnModel() { UserId = userId, IsSucess = true, Error = null};

        var allUserClaim = await userManager.GetClaimsAsync(user);
        foreach (var claim in allUserClaim)
        {
            result.AllUserClaims[claim.Type] = claim.Value;
        }
        
        return Ok(result);
    }

    [HttpPost]
    public async Task<ActionResult<ChangeEmailRespond>> UpdateUserEmail([FromBody] ChangeEmailRequest request)
    {
        logger.LogInformation("UpdateUserEmail called with NewEmail: {NewEmail}", request.NewEmail);
        
        var response = new ChangeEmailRespond
        {
            NewEmail = request.NewEmail
        };

        var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);

        if (string.IsNullOrEmpty(userId))
        {
            response.Problems.Add("UserId", "User ID not found in token.");
            logger.LogWarning("UserId not found in token");
            return BadRequest(response);
        }

        var user = await userManager.FindByIdAsync(userId);
        if (user == null)
        {
            response.Problems.Add("User", "User not found.");
            logger.LogWarning("User not found: {UserId}", userId);
            return Unauthorized(response);
        }

        response.OldEmail = user.Email;
        
        if (string.IsNullOrEmpty(request.NewEmail))
        {
            response.Problems.Add("NewEmail", "New email is required.");
            logger.LogWarning("New email is required for UserId: {UserId}", userId);
            return BadRequest(response);
        }

        user.Email = request.NewEmail;
        var result = await userManager.UpdateAsync(user);
        if (result.Succeeded)
        {
            response.ValSuccess = true;
            response.UpdateSuccess = true;
            logger.LogInformation("User email updated successfully: {UserId}", userId);
            return Ok(response);
        }
        else
        {
            response.ValSuccess = true; 
            response.Problems = result.Errors.ToDictionary(
                error => error.Code, 
                error => error.Description
            );
            logger.LogWarning("Failed to update user email for UserId: {UserId}", userId);
            return StatusCode(418, response);
        }
    }

    [HttpPost]
    public async Task<ActionResult<ChangePhoneNumberRespond>> UpdatePhoneNumber([FromBody] ChangePhoneNumberRequest request)
    {
        logger.LogInformation("UpdatePhoneNumber called with Prefix: {Prefix}, Number: {Number}", request.Prefix, request.Number);
        
        var response = new ChangePhoneNumberRespond
        {
            Prefix = request.Prefix,
            Number = request.Number
        };

        var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);

        if (string.IsNullOrEmpty(userId))
        {
            response.Problems.Add("UserId", "User ID not found in token.");
            logger.LogWarning("UserId not found in token");
            return BadRequest(response);
        }

        var user = await userManager.FindByIdAsync(userId);
        if (user == null)
        {
            response.Problems.Add("User", "User not found.");
            logger.LogWarning("User not found: {UserId}", userId);
            return Unauthorized(response);
        }

        response.OldPrefix = user.DialCode; 
        response.OldPhone = user.PhoneNumber;
        
        if (string.IsNullOrEmpty(request.Prefix) || string.IsNullOrEmpty(request.Number))
        {
            response.Problems.Add("PhoneNumber", "Prefix and number are required.");
            logger.LogWarning("Prefix or number missing for UserId: {UserId}", userId);
            return BadRequest(response);
        }

        user.PhoneNumber = request.Number;
        user.DialCode = request.Prefix;
        
        var result = await userManager.UpdateAsync(user);
        if (result.Succeeded)
        {
            response.ValSuccess = true;
            response.UpdateSuccess = true;
            logger.LogInformation("User phone number updated successfully: {UserId}", userId);
            return Ok(response);
        }
        else
        {
            response.ValSuccess = true;
            response.Problems = result.Errors.ToDictionary(
                error => error.Code,
                error => error.Description
            );
            logger.LogWarning("Failed to update phone number for UserId: {UserId}", userId);
            return StatusCode(418, response);
        }
    }

    [HttpPost]
    public async Task<ActionResult<AccountDetailRespond>> GetMyDetail()
    {
        var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
        var response = new AccountDetailRespond();
        
        
        if (string.IsNullOrEmpty(userId))
        {
            response.Problems.Add("UserId", "User ID not found in token.");
            logger.LogWarning("UserId not found in token");
            return BadRequest(response);
        }

        var user = await userManager.FindByIdAsync(userId);
        
        if (user == null)
        {
            response.Problems.Add("User", "User not found.");
            logger.LogWarning("User not found: {UserId}", userId);
            return Unauthorized(response);
        }

        response.Id = user.Id;
        response.Email = user.Email;
        response.UserName = user.UserName;
        response.Phone = user.PhoneNumber;
        response.DialCode = user.DialCode;

        return Ok(response);
    }

    [HttpPost]
    [HasPrivilege(GlobalPrivilage.Privilege.GlobalEditUser, GlobalPrivilage.Privilege.GlobalDeleteUser)]
    public async Task<ActionResult<AccountListsResponse>> GetListOfUsers([FromBody] AccountListsRequest request)
    {
        if (request.Page < 1 || request.PageSize < 1)
        {
            return BadRequest("Page and page size must be greater than 0.");
        }

        var result = new AccountListsResponse
        {
            AccountsDetail = await userManager.Users
                .OrderBy(u => u.UserName)
                .Skip((request.Page - 1) * request.PageSize)
                .Take(request.PageSize)
                .Select(q
                    =>new AccountDetailRespond()
                    {
                        UserName = q.UserName, DialCode = q.DialCode, Email = q.Email, Id = q.Id, Phone = q.PhoneNumber
                    }).ToListAsync(),
            Page = request.Page,
            PageSize = request.PageSize,
            NextPageExist  = await userManager.Users
                .OrderBy(u => u.UserName)
                .Skip((request.Page) * request.PageSize)
                .Take(request.PageSize).AnyAsync(),
            LastPageNum = (await userManager.Users.CountAsync()) / request.PageSize
        };
        
        return result;
    }

    [HttpPost]
    [HasPrivilege(GlobalPrivilage.Privilege.GlobalEditUser)]
    public async Task<ActionResult<EditAccountRespond>> EditAccount([FromBody] EditAccountRequest request)
    {
        if (request is { UserId: null })
        {
            return BadRequest(new EditAccountRespond()
            {
                Message = "Invalid request", Success = false
            });
        }
        
        var user = await userManager.FindByIdAsync(request.UserId);

        if (user == null)
        {
            return BadRequest(new EditAccountRespond()
            {
                Message = "User not found", Success = false
            });
        }
        
        var respond = new EditAccountRespond();
        
        if (user.UserName != request.NewUserName)
        {
            respond.NewUserName = request.NewUserName;
            user.UserName = request.NewUserName;
            respond.UserNameEdited = true;
        }

        if (user.Email != request.NewEmail)
        {
            respond.NewEmail = request.NewEmail;
            user.Email = request.NewEmail;
            respond.EmailEdited = true;
        }

        if (user.DialCode != request.NewDialCode || user.PhoneNumber != request.NewPhone)
        {
            respond.NewDialCode = request.NewDialCode;
            respond.NewPhone = request.NewPhone;

            user.PhoneNumber = request.NewPhone;
            user.DialCode = request.NewDialCode;
            respond.PhoneEdited = true;

        }

        var result = await userManager.UpdateAsync(user);

        respond.Success = result.Succeeded;
        respond.Message = string.Join("\n",result.Errors.Select(error => $"{error.Code}:{error.Description}"));
        return respond;
    }
}
