using Application.Abstractions.Data; using Application.Abstractions.Hub; using Application.Abstractions.Messaging; using Application.Abstractions.YouTube; using Domain.Entities.Members; using Domain.Entities.Members.ValueObject; using Microsoft.EntityFrameworkCore; using SharedKernel.Results; namespace Application.Features.Admin.Channel.YouTubePubSub.ForceLive; internal sealed class Handler( IAppDbContext db, IYouTubeLiveStateStore liveStateStore, IChannelStatusBroadcaster broadcaster ) : ICommandHandler { public async Task Handle(Command request, CancellationToken ct) { if (string.IsNullOrWhiteSpace(request.YouTubeChannelID) || string.IsNullOrWhiteSpace(request.VideoID)) { return Result.Failure(Error.Problem("ForceLive.Invalid", "YouTube Channel ID 와 Video ID 가 필요합니다.")); } var channel = await db.Channel.AsNoTracking() .FirstOrDefaultAsync(c => c.YouTubeChannelID == request.YouTubeChannelID && c.IsActive, ct); if (channel is null) { return Result.Failure(Error.NotFound("ForceLive.ChannelNotFound", "내부 Channel 을 찾을 수 없습니다.")); } var title = string.IsNullOrWhiteSpace(request.Title) ? "(수동 등록 라이브)" : request.Title!; var now = DateTime.UtcNow; // YouTube API 호출 없이 LiveStreamInfo 수동 생성 — quota 0 var liveInfo = new YouTubeLiveStreamInfo( VideoId: request.VideoID, Title: title, ChannelId: request.YouTubeChannelID, LiveBroadcastContent: "live", ActiveLiveChatId: null, ScheduledStartTime: null, ActualStartTime: now, ViewerCount: 0, ViewerCountUpdatedAt: now ); // Redis 라이브 상태 저장 await liveStateStore.SetLiveAsync(request.YouTubeChannelID, liveInfo); // DB BroadcastSession 생성 (없으면) var existing = await db.BroadcastSession .FirstOrDefaultAsync(b => b.Platform == BroadcastPlatform.YouTube && b.VideoID == request.VideoID, ct); if (existing is null) { var session = BroadcastSession.Create(channel.ID, BroadcastPlatform.YouTube, request.VideoID); session.UpdateStats(title, now, null, 0, 0); db.BroadcastSession.Add(session); await db.SaveChangesAsync(ct); } // SignalR 브로드캐스트 → ChannelSidebar / WatchView 실시간 반영 await broadcaster.BroadcastAsync(channel.SID, isLive: true, viewerCount: 0, videoId: request.VideoID, ct); return Result.Success(); } }