using Microsoft.EntityFrameworkCore; using SVSim.Database.Entities.Guild; using SVSim.Database.Repositories.Guild; using SVSim.Database.Repositories.Viewer; using SVSim.Database.Services; namespace SVSim.Database.Services.Guild; public sealed class GuildService : IGuildService { private readonly IGuildRepository _guilds; private readonly IGuildMemberRepository _members; private readonly IGuildInviteRepository _invites; private readonly IGuildJoinRequestRepository _joinRequests; private readonly IGuildChatMessageRepository _chatMessages; private readonly IGameConfigService _config; private readonly IGuildIdGenerator _idGen; private readonly IGuildChatService _chat; private readonly IViewerRepository _viewers; private readonly SVSimDbContext _db; public GuildService( IGuildRepository guilds, IGuildMemberRepository members, IGuildInviteRepository invites, IGuildJoinRequestRepository joinRequests, IGuildChatMessageRepository chatMessages, IGameConfigService config, IGuildIdGenerator idGen, IGuildChatService chat, IViewerRepository viewers, SVSimDbContext db) { _guilds = guilds; _members = members; _invites = invites; _joinRequests = joinRequests; _chatMessages = chatMessages; _config = config; _idGen = idGen; _chat = chat; _viewers = viewers; _db = db; } public async Task GetMyGuildAsync(long viewerId, CancellationToken ct = default) { var membership = await _members.GetMembershipAsync(viewerId, ct); if (membership is null) return null; var guild = await _guilds.GetWithMembersAsync(membership.GuildId, ct); if (guild is null || guild.BreakupAt is not null) return null; int joinReqCount = await _joinRequests.CountPendingForGuildAsync(guild.GuildId, ct); int inviteCount = await _invites.CountPendingForInviteeAsync(viewerId, ct); return new GuildFullView(guild, guild.Members, joinReqCount, inviteCount); } public Task GetActiveAsync(int guildId, CancellationToken ct = default) => _guilds.GetActiveByIdAsync(guildId, ct); public Task> SearchAsync(string name, int activity, int joinCondition, int memberBucket, CancellationToken ct = default) => throw new NotImplementedException(); public async Task CreateAsync(long viewerId, CreateGuildRequest req, CancellationToken ct = default) { if (string.IsNullOrWhiteSpace(req.Name) || req.Name.Length > 64) return new(GuildOpResultCode.NameInvalid); if (req.Activity is < 1 or > 16) return new(GuildOpResultCode.NameInvalid); if (req.JoinCondition is < 1 or > 3) return new(GuildOpResultCode.NameInvalid); if (await _members.GetMembershipAsync(viewerId, ct) is not null) return new(GuildOpResultCode.AlreadyInGuild); if (await _guilds.NameExistsAsync(req.Name, ct)) return new(GuildOpResultCode.NameTaken); var viewer = await _db.Viewers .Include(v => v.Info.SelectedEmblem) .FirstOrDefaultAsync(v => v.Id == viewerId, ct); if (viewer is null) return new(GuildOpResultCode.PermissionDenied); var id = await _idGen.NextAsync( (cand, c) => _guilds.GetByIdAsync(cand, c).ContinueWith(t => t.Result is not null, c), ct); var now = DateTime.UtcNow; var g = new Entities.Guild.Guild { GuildId = id, Name = req.Name, Description = "", LeaderViewerId = viewerId, EmblemId = DefaultEmblemId(viewer), Activity = (GuildActivity)req.Activity, JoinCondition = (GuildJoinCondition)req.JoinCondition, CreatedAt = now, }; await _guilds.AddAsync(g, ct); await _members.AddAsync( new GuildMember { GuildId = id, ViewerId = viewerId, Role = GuildRole.Leader, JoinedAt = now }, ct); // Side-effects: clear any pending invites + cancel pending join requests for this viewer. await _invites.ConsumePendingForViewerAsync(viewerId, now, ct); await _joinRequests.CancelPendingForViewerAsync(viewerId, now, ct); // Update Viewer.GuildId pointer for quick lookups. viewer.GuildId = id; await _db.SaveChangesAsync(ct); await _chat.EmitSystemEventAsync(id, viewerId, GuildChatMessageType.CreateGuild, body: null, ct); return new(GuildOpResultCode.Ok, GuildId: id); } /// /// Returns the viewer's currently-equipped emblem id, or 100000000 (default) if none. /// private static long DefaultEmblemId(Models.Viewer viewer) { var emblemId = viewer.Info?.SelectedEmblem?.Id; return emblemId is > 0 ? emblemId.Value : 100_000_000L; } public async Task UpdateAsync(long viewerId, UpdateGuildRequest req, CancellationToken ct = default) { var m = await _members.GetMembershipAsync(viewerId, ct); if (m is null) return new(GuildOpResultCode.NotInGuild); if (m.Role != GuildRole.Leader) return new(GuildOpResultCode.PermissionDenied); if (req.Name is not null && (string.IsNullOrWhiteSpace(req.Name) || req.Name.Length > 64)) return new(GuildOpResultCode.NameInvalid); if (req.Activity.HasValue && req.Activity.Value is < 1 or > 16) return new(GuildOpResultCode.NameInvalid); if (req.JoinCondition.HasValue && req.JoinCondition.Value is < 1 or > 3) return new(GuildOpResultCode.NameInvalid); await _guilds.UpdateActivityAndJoinConditionAsync(m.GuildId, req.Activity, req.JoinCondition, req.Name, ct); return GuildOpResult.Ok; } public async Task UpdateDescriptionAsync(long viewerId, string description, CancellationToken ct = default) { var m = await _members.GetMembershipAsync(viewerId, ct); if (m is null) return new(GuildOpResultCode.NotInGuild); if (m.Role != GuildRole.Leader) return new(GuildOpResultCode.PermissionDenied); if (description.Length > 512) return new(GuildOpResultCode.NameInvalid); await _guilds.UpdateDescriptionAsync(m.GuildId, description, ct); await _chat.EmitSystemEventAsync(m.GuildId, viewerId, GuildChatMessageType.Description, body: null, ct); return GuildOpResult.Ok; } public async Task UpdateEmblemAsync(long viewerId, long emblemId, CancellationToken ct = default) { var m = await _members.GetMembershipAsync(viewerId, ct); if (m is null) return new(GuildOpResultCode.NotInGuild); if (m.Role != GuildRole.Leader) return new(GuildOpResultCode.PermissionDenied); await _guilds.UpdateEmblemAsync(m.GuildId, emblemId, ct); return GuildOpResult.Ok; } public async Task BreakupAsync(long viewerId, CancellationToken ct = default) { var membership = await _members.GetMembershipAsync(viewerId, ct); if (membership is null) return new(GuildOpResultCode.NotInGuild); if (membership.Role != GuildRole.Leader) return new(GuildOpResultCode.PermissionDenied); var now = DateTime.UtcNow; var members = await _members.ListByGuildAsync(membership.GuildId, ct); foreach (var m in members) await _viewers.ClearGuildIdAsync(m.ViewerId, ct); await _chatMessages.DeleteAllForGuildAsync(membership.GuildId, ct); await _invites.DeleteAllForGuildAsync(membership.GuildId, ct); await _joinRequests.DeleteAllForGuildAsync(membership.GuildId, ct); await _members.DeleteAllForGuildAsync(membership.GuildId, ct); await _guilds.MarkBrokenUpAsync(membership.GuildId, now, ct); return GuildOpResult.Ok; } public Task InviteAsync(long callerViewerId, long targetViewerId, CancellationToken ct = default) => throw new NotImplementedException(); public Task CancelInviteAsync(long callerViewerId, long targetViewerId, CancellationToken ct = default) => throw new NotImplementedException(); public Task RejectInviteAsync(long callerViewerId, int guildId, CancellationToken ct = default) => throw new NotImplementedException(); public Task JoinAsync(long viewerId, int guildId, CancellationToken ct = default) => throw new NotImplementedException(); public Task CancelJoinRequestAsync(long viewerId, int guildId, CancellationToken ct = default) => throw new NotImplementedException(); public Task AcceptJoinRequestAsync(long callerViewerId, long applicantViewerId, CancellationToken ct = default) => throw new NotImplementedException(); public Task RejectJoinRequestAsync(long callerViewerId, long applicantViewerId, CancellationToken ct = default) => throw new NotImplementedException(); public Task LeaveAsync(long viewerId, CancellationToken ct = default) => throw new NotImplementedException(); public Task RemoveAsync(long callerViewerId, long targetViewerId, CancellationToken ct = default) => throw new NotImplementedException(); public Task ChangeRoleAsync(long callerViewerId, long targetViewerId, int newRoleId, CancellationToken ct = default) => throw new NotImplementedException(); public Task> ListPendingInvitesForMeAsync(long viewerId, CancellationToken ct = default) => throw new NotImplementedException(); public Task> ListPendingJoinRequestsForMyGuildAsync(long viewerId, CancellationToken ct = default) => throw new NotImplementedException(); }