using Fengling.Console.Models.Dtos;
using Fengling.Platform.Domain.AggregatesModel.UserAggregate;
using Fengling.Platform.Domain.AggregatesModel.RoleAggregate;
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
using Fengling.Platform.Infrastructure;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using System.Security.Claims;

namespace Fengling.Console.Services;

public interface IUserService
{
    Task<(IEnumerable<UserDto> Items, int TotalCount)> GetUsersAsync(int page, int pageSize, string? userName = null, string? email = null, string? tenantId = null);
    Task<UserDto?> GetUserAsync(long id);
    Task<UserDto> CreateUserAsync(CreateUserDto dto);
    Task<UserDto> UpdateUserAsync(long id, UpdateUserDto dto);
    Task ResetPasswordAsync(long id, ResetPasswordDto dto);
    Task DeleteUserAsync(long id);
}

public class UserService(
    ITenantManager tenantManager,
    UserManager<ApplicationUser> userManager,
    RoleManager<ApplicationRole> roleManager,
    PlatformDbContext context,
    IHttpContextAccessor httpContextAccessor)
    : IUserService
{
    public async Task<(IEnumerable<UserDto> Items, int TotalCount)> GetUsersAsync(int page, int pageSize, string? userName = null, string? email = null, string? tenantId = null)
    {
        var query = context.Users.AsQueryable();

        if (!string.IsNullOrEmpty(userName))
        {
            query = query.Where(u => u.UserName != null && u.UserName.Contains(userName));
        }

        if (!string.IsNullOrEmpty(email))
        {
            query = query.Where(u => u.Email != null && u.Email.Contains(email));
        }

        if (!string.IsNullOrEmpty(tenantId))
        {
            query = query.Where(u => u.TenantInfo.TenantId.ToString().Contains(tenantId));
        }

        var totalCount = await query.CountAsync();
        var users = await query
            .OrderByDescending(u => u.CreatedTime)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync();

        var userDtos = new List<UserDto>();
        foreach (var user in users)
        {
            var roles = await userManager.GetRolesAsync(user);
            var tenant = user.TenantInfo.TenantId > 0 ? await tenantManager.FindByIdAsync(user.TenantInfo.TenantId) : null;
            userDtos.Add(new UserDto
            {
                Id = user.Id,
                UserName = user.UserName,
                Email = user.Email,
                RealName = user.RealName,
                Phone = user.Phone,
                TenantCode = user.TenantInfo.TenantId.ToString(),
                TenantName = tenant?.Name ?? "",
                Roles = roles.ToList(),
                EmailConfirmed = user.EmailConfirmed,
                IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow,
                CreatedAt = user.CreatedTime
            });
        }

        return (userDtos, totalCount);
    }

    public async Task<UserDto?> GetUserAsync(long id)
    {
        var user = await context.Users.FindAsync(id);
        if (user == null) return null;

        var roles = await userManager.GetRolesAsync(user);
        var tenant = user.TenantInfo.TenantId > 0 ? await tenantManager.FindByIdAsync(user.TenantInfo.TenantId) : null;
        return new UserDto
        {
            Id = user.Id,
            UserName = user.UserName,
            Email = user.Email,
            RealName = user.RealName,
            Phone = user.Phone,
            TenantCode = user.TenantInfo.TenantId.ToString(),
            TenantName = tenant?.Name ?? "",
            Roles = roles.ToList(),
            EmailConfirmed = user.EmailConfirmed,
            IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow,
            CreatedAt = user.CreatedTime
        };
    }

    public async Task<UserDto> CreateUserAsync(CreateUserDto dto)
    {
        Tenant? tenant = null;

        if (dto.TenantId.HasValue && dto.TenantId.Value > 0)
        {
            tenant = await tenantManager.FindByIdAsync(dto.TenantId.Value);
            if (tenant == null)
            {
                throw new InvalidOperationException("Invalid tenant ID");
            }
        }

        var tenantInfo = tenant != null
            ? new TenantInfo(tenant.Id, tenant.TenantCode, tenant.Name)
            : new TenantInfo(0, "", "");

        var user = new ApplicationUser
        {
            UserName = dto.UserName,
            Email = dto.Email,
            RealName = dto.RealName,
            Phone = dto.Phone,
            TenantInfo = tenantInfo,
            EmailConfirmed = dto.EmailConfirmed,
            CreatedTime = DateTime.UtcNow
        };

        var result = await userManager.CreateAsync(user, dto.Password);
        if (!result.Succeeded)
        {
            throw new InvalidOperationException(string.Join(", ", result.Errors.Select(e => e.Description)));
        }

        if (dto.RoleIds != null && dto.RoleIds.Any())
        {
            foreach (var roleId in dto.RoleIds)
            {
                var role = await roleManager.FindByIdAsync(roleId.ToString());
                if (role != null)
                {
                    await userManager.AddToRoleAsync(user, role.Name!);
                }
            }
        }

        if (!dto.IsActive)
        {
            await userManager.SetLockoutEnabledAsync(user, true);
            await userManager.SetLockoutEndDateAsync(user, DateTimeOffset.MaxValue);
        }

        await CreateAuditLog("user", "create", "User", user.Id, user.UserName, null, System.Text.Json.JsonSerializer.Serialize(dto));

        var roles = await userManager.GetRolesAsync(user);
        return new UserDto
        {
            Id = user.Id,
            UserName = user.UserName,
            Email = user.Email,
            RealName = user.RealName,
            Phone = user.Phone,
            TenantCode = user.TenantInfo.TenantId.ToString(),
            TenantName = tenant?.Name ?? "",
            Roles = roles.ToList(),
            EmailConfirmed = user.EmailConfirmed,
            IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow,
            CreatedAt = user.CreatedTime
        };
    }

    public async Task<UserDto> UpdateUserAsync(long id, UpdateUserDto dto)
    {
        var user = await context.Users.FindAsync(id);
        if (user == null)
        {
            throw new KeyNotFoundException($"User with ID {id} not found");
        }

        var oldValue = System.Text.Json.JsonSerializer.Serialize(user);

        user.Email = dto.Email;
        user.RealName = dto.RealName;
        user.Phone = dto.Phone;
        user.EmailConfirmed = dto.EmailConfirmed;
        user.UpdatedTime = DateTime.UtcNow;

        await userManager.UpdateAsync(user);

        if (dto.IsActive)
        {
            await userManager.SetLockoutEnabledAsync(user, false);
            await userManager.SetLockoutEndDateAsync(user, null);
        }
        else
        {
            await userManager.SetLockoutEnabledAsync(user, true);
            await userManager.SetLockoutEndDateAsync(user, DateTimeOffset.MaxValue);
        }

        var tenant = user.TenantInfo.TenantId > 0 ? await tenantManager.FindByIdAsync(user.TenantInfo.TenantId) : null;

        await CreateAuditLog("user", "update", "User", user.Id, user.UserName, oldValue, System.Text.Json.JsonSerializer.Serialize(user));

        var roles = await userManager.GetRolesAsync(user);
        return new UserDto
        {
            Id = user.Id,
            UserName = user.UserName,
            Email = user.Email,
            RealName = user.RealName,
            Phone = user.Phone,
            TenantCode = user.TenantInfo.TenantId.ToString(),
            TenantName = tenant?.Name ?? "",
            Roles = roles.ToList(),
            EmailConfirmed = user.EmailConfirmed,
            IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow,
            CreatedAt = user.CreatedTime
        };
    }

    public async Task ResetPasswordAsync(long id, ResetPasswordDto dto)
    {
        var user = await userManager.FindByIdAsync(id.ToString());
        if (user == null)
        {
            throw new KeyNotFoundException($"User with ID {id} not found");
        }

        var token = await userManager.GeneratePasswordResetTokenAsync(user);
        var result = await userManager.ResetPasswordAsync(user, token, dto.NewPassword);

        if (!result.Succeeded)
        {
            throw new InvalidOperationException(string.Join(", ", result.Errors.Select(e => e.Description)));
        }

        await CreateAuditLog("user", "reset_password", "User", user.Id, user.UserName);
    }

    public async Task DeleteUserAsync(long id)
    {
        var user = await context.Users.FindAsync(id);
        if (user == null)
        {
            throw new KeyNotFoundException($"User with ID {id} not found");
        }

        var oldValue = System.Text.Json.JsonSerializer.Serialize(user);
        user.IsDeleted = true;
        user.UpdatedTime = DateTime.UtcNow;
        await context.SaveChangesAsync();

        await CreateAuditLog("user", "delete", "User", user.Id, user.UserName, oldValue);
    }

    private async Task CreateAuditLog(string operation, string action, string targetType, long? targetId, string? targetName, string? oldValue = null, string? newValue = null)
    {
        var httpContext = httpContextAccessor.HttpContext;
        var userName = httpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier) ?? httpContext?.User?.Identity?.Name ?? "system";
        var tenantIdClaim = httpContext?.User?.FindFirstValue("TenantId");

        var log = new AuditLog
        {
            Operator = userName,
            TenantId = tenantIdClaim,
            Operation = operation,
            Action = action,
            TargetType = targetType,
            TargetId = targetId,
            TargetName = targetName,
            IpAddress = httpContext?.Connection?.RemoteIpAddress?.ToString() ?? "unknown",
            Status = "success",
            OldValue = oldValue,
            NewValue = newValue,
            CreatedAt = DateTime.UtcNow
        };

        context.AuditLogs.Add(log);
        await context.SaveChangesAsync();
    }
}
