using ERPCore.Domain.Entities;
using ERPCore.Domain.Enums;
using ERPCore.Dtos.Auth;
using ERPCore.Dtos.Common;
using ERPCore.Dtos.Users;
using ERPCore.Infra.Auth.AuthHex;
using ERPCore.Infra.UoW;
using ERPCore.Repositories.Interfaces;
using ERPCore.Services.Interfaces;
using ERPCore.System.Errors;
using Microsoft.EntityFrameworkCore;

namespace ERPCore.Services;

public sealed class UserManagementService : IUserManagementService
{
    private readonly IRepository<User> _users;
    private readonly IRepository<Role> _roles;
    private readonly IAuthUserService _authUsers;
    private readonly IAuthHexClient _authHex;
    private readonly IEmployeeUserLinkService _links;
    private readonly IUnitOfWork _uow;

    public UserManagementService(
        IRepository<User> users, IRepository<Role> roles, IAuthUserService authUsers, IAuthHexClient authHex,
        IEmployeeUserLinkService links, IUnitOfWork uow)
    {
        _users = users;
        _roles = roles;
        _authUsers = authUsers;
        _authHex = authHex;
        _links = links;
        _uow = uow;
    }

    public async Task<PagedResponse<ManagedUserDto>> ListAsync(PageQuery query, CancellationToken ct = default)
    {
        IQueryable<User> q = _users.Query().AsNoTracking().Include(u => u.Role);
        if (!string.IsNullOrWhiteSpace(query.Q))
        {
            var term = query.Q.Trim();
            q = q.Where(u => EF.Functions.ILike(u.Username, $"%{term}%") || EF.Functions.ILike(u.DisplayName, $"%{term}%"));
        }

        var total = await q.CountAsync(ct);
        var rows = await q.OrderBy(u => u.Username)
            .Skip(query.Skip).Take(query.PageSize)
            .ToListAsync(ct);

        return PagedResponse<ManagedUserDto>.Create(rows.Select(Map).ToList(), query.Page, query.PageSize, total);
    }

    public async Task<ManagedUserDto?> GetAsync(int userId, CancellationToken ct = default)
    {
        var user = await _users.Query().AsNoTracking().Include(u => u.Role)
            .FirstOrDefaultAsync(u => u.UserId == userId, ct);
        return user is null ? null : Map(user);
    }

    public async Task<ManagedUserDto> CreateAsync(CreateUserRequest request, CancellationToken ct = default)
    {
        var role = await _roles.GetByIdAsync(request.RoleId, ct)
            ?? throw new NotFoundException($"Role {request.RoleId} was not found.");

        var username = request.Username.Trim();
        if (await _users.Query().AnyAsync(u => u.Username == username, ct))
            throw new ConflictException($"A user with username '{username}' already exists.");

        var authUserId = Guid.NewGuid();

        // Source of truth: AuthHex creates the credential + emails it (registerUser,
        // ERP_Auth_Service/Services/UserManager/UserManagerService.cs).
        await _authUsers.RegisterAsync(new RegisterRequest
        {
            UserId = authUserId,
            RoleId = role.AuthRoleId,
            UserTypeId = request.UserTypeId,
            Fullname = request.FullName.Trim(),
            UserName = username,
            Nic = request.Nic,
            Email = request.Email.Trim(),
            MobileNumber = request.MobileNumber,
            Password = request.Password,
            ChkUser = true
        }, ct);

        // Mirror into the local shadow User row immediately, rather than waiting
        // for ShadowUserClaimsTransformation's next-login JIT provisioning. Email is
        // persisted here too — it is the field the Employee<->User cross-link
        // (docs/12-BACKEND-HRM.md A.5) matches on.
        var user = new User
        {
            AuthUserId = authUserId,
            Username = username,
            DisplayName = request.FullName.Trim(),
            Email = request.Email.Trim(),
            RoleId = role.RoleId,
            Status = EntityStatus.Active
        };

        await _users.AddAsync(user, ct);
        await _uow.SaveChangesAsync(ct);

        // Explicit, human-confirmed link to an existing Staff record (never automatic).
        if (request.LinkEmployeeId is not null)
            await _links.LinkAsync(request.LinkEmployeeId.Value, user.UserId, ct);

        user.Role = role;
        return Map(user);
    }

    public async Task<ManagedUserDto> UpdateRoleAsync(int userId, UpdateUserRoleRequest request, CancellationToken ct = default)
    {
        var user = await _users.GetByIdAsync(userId, ct)
            ?? throw new NotFoundException($"User {userId} was not found.");
        var role = await _roles.GetByIdAsync(request.RoleId, ct)
            ?? throw new NotFoundException($"Role {request.RoleId} was not found.");

        user.RoleId = role.RoleId;
        await _uow.SaveChangesAsync(ct);

        user.Role = role;
        return Map(user);
    }

    public async Task<List<UserTypeOptionDto>> ListUserTypesAsync(CancellationToken ct = default)
    {
        var userTypes = await _authHex.ListUserTypesAsync(ct);
        return userTypes.Select(t => new UserTypeOptionDto(t.UserTypeId, t.Code, t.Description)).ToList();
    }

    private static ManagedUserDto Map(User u) => new(
        u.UserId, u.Username, u.DisplayName, u.Email, u.Status, u.RoleId, u.Role?.Code, u.Role?.Name);
}
