using CashLineERP.Application.Identity;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;

namespace CashLineERP.Infrastructure.Persistence.Repositories;

public sealed class IdentityReadRepository : IIdentityReadRepository
{
    private readonly CashLineErpDbContext _dbContext;

    public IdentityReadRepository(CashLineErpDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public ApplicationUserIdentityView? FindByLoginName(string loginName)
    {
        var user = _dbContext.Users
            .AsNoTracking()
            .Where(user => user.LoginName == loginName)
            .Select(MapIdentityView())
            .SingleOrDefault();

        if (user is null)
        {
            return null;
        }

        return user with { RoleCodes = GetRoleCodes(user.UserId), FunctionCodes = GetFunctionCodes(user.UserId) };
    }

    public ApplicationUserIdentityView? FindByUserId(long userId)
    {
        var user = _dbContext.Users
            .AsNoTracking()
            .Where(user => user.Id == userId)
            .Select(MapIdentityView())
            .SingleOrDefault();

        if (user is null)
        {
            return null;
        }

        return user with { RoleCodes = GetRoleCodes(user.UserId), FunctionCodes = GetFunctionCodes(user.UserId) };
    }

    public IReadOnlyCollection<RoleSummaryView> GetRoles()
    {
        return _dbContext.Roles
            .AsNoTracking()
            .Where(role => role.IsActive)
            .OrderBy(role => role.RoleCode)
            .Select(role => new RoleSummaryView(
                role.RoleCode,
                role.RoleName,
                role.PortalCode,
                role.IsChecker))
            .ToArray();
    }

    private static Expression<Func<Domain.Identity.ApplicationUser, ApplicationUserIdentityView>> MapIdentityView()
    {
        return user => new ApplicationUserIdentityView(
            user.Id,
            user.LoginName,
            user.DisplayName,
            user.UserTypeCode,
            user.PasswordHash,
            user.Email,
            user.BranchId,
            user.PartyId,
            Array.Empty<string>(),
            Array.Empty<string>(),
            user.IsActive,
            user.IsLocked);
    }

    private string[] GetRoleCodes(long userId)
    {
        return _dbContext.UserRoles
            .AsNoTracking()
            .Where(entry => entry.UserId == userId && entry.IsActive)
            .Join(
                _dbContext.Roles.AsNoTracking().Where(role => role.IsActive),
                userRole => userRole.RoleId,
                role => role.Id,
                (_, role) => role.RoleCode)
            .OrderBy(roleCode => roleCode)
            .ToArray();
    }

    private string[] GetFunctionCodes(long userId)
    {
        return _dbContext.UserFunctionAssignments
            .AsNoTracking()
            .Where(entry => entry.UserId == userId && entry.IsActive)
            .Select(entry => entry.FunctionCode)
            .OrderBy(functionCode => functionCode)
            .ToArray();
    }
}
