using Microsoft.Extensions.Logging;
using SameBeat.Application.DTO;
using SameBeat.Application.Interfejsy;
using SameBeat.Application.Komendy;
using SameBeat.Domain.Encje;
using SameBeat.Domain.Enumy;
using SameBeat.Domain.Wyjatki;
namespace SameBeat.Application.Serwisy;
///
/// Logika poczekalni -- dolaczanie nowych uczestnikow do biezacego wydarzenia (z tozsamoscia LiveKit
/// generowana serwerowo) oraz zarzadzanie nimi przez hosta. Akceptacja/wpuszczenie na scene to ETAP 3 --
/// tutaj host moze tylko odrzucic albo zbanowac.
///
public sealed class SerwisPoczekalni : ISerwisPoczekalni
{
private readonly IRepozytoriumWydarzen _repozytoriumWydarzen;
private readonly IRepozytoriumPoczekalni _repozytoriumPoczekalni;
private readonly ISerwisTokenowLiveKit _serwisTokenowLiveKit;
private readonly IUslugaHaszowaniaSekretowSesji _uslugaHaszowaniaSekretowSesji;
private readonly IJednostkaPracy _jednostkaPracy;
private readonly string _urlLiveKit;
private readonly string _nazwaPokojuPoczekalnia;
private readonly ILogger _logger;
public SerwisPoczekalni(
IRepozytoriumWydarzen repozytoriumWydarzen,
IRepozytoriumPoczekalni repozytoriumPoczekalni,
ISerwisTokenowLiveKit serwisTokenowLiveKit,
IUslugaHaszowaniaSekretowSesji uslugaHaszowaniaSekretowSesji,
IJednostkaPracy jednostkaPracy,
string urlLiveKit,
string nazwaPokojuPoczekalnia,
ILogger logger)
{
_repozytoriumWydarzen = repozytoriumWydarzen;
_repozytoriumPoczekalni = repozytoriumPoczekalni;
_serwisTokenowLiveKit = serwisTokenowLiveKit;
_uslugaHaszowaniaSekretowSesji = uslugaHaszowaniaSekretowSesji;
_jednostkaPracy = jednostkaPracy;
_urlLiveKit = urlLiveKit;
_nazwaPokojuPoczekalnia = nazwaPokojuPoczekalnia;
_logger = logger;
}
public async Task> DolaczAsync(DolaczKomenda komenda, CancellationToken token = default)
{
// "Biezace" wydarzenie = najswiezsze utworzone, ta sama definicja co uzywa juz ISerwisWydarzen
// dla publicznego statusu na stronie glownej.
var wydarzenie = await _repozytoriumWydarzen.PobierzOstatnieAsync(token);
if (wydarzenie is null)
{
_logger.LogInformation("Proba dolaczenia do poczekalni bez zadnego wydarzenia w bazie.");
return WynikOperacji.Niepowodzenie("Brak aktywnego wydarzenia, sprobuj pozniej.");
}
UczestnikOczekujacy uczestnik;
try
{
uczestnik = UczestnikOczekujacy.Dolacz(wydarzenie.Id, komenda.Nick);
}
catch (WyjatekDomenowy wyjatek)
{
return WynikOperacji.Niepowodzenie(wyjatek.Message);
}
if (string.IsNullOrWhiteSpace(_urlLiveKit))
{
// Ten sam powod co w SerwisDostepuLiveKit -- brak konfiguracji LiveKit ma dac czytelny
// blad biznesowy, nie NullReferenceException/500 na swiezym srodowisku.
_logger.LogWarning("Dolaczenie odrzucone -- adres serwera LiveKit nie jest skonfigurowany.");
return WynikOperacji.Niepowodzenie("Usluga LiveKit nie jest jeszcze skonfigurowana na serwerze.");
}
// Sekret sesji (ETAP 3): jedyny dowod tozsamosci "to ja", ktory uczestnik bez konta bedzie mogl
// pozniej pokazac (status w poczekalni, wymiana na token sceny). Jawna wartosc leci TYLKO w tej
// odpowiedzi -- w bazie lÄ…duje wylacznie jej hash, dokladnie jak przy hasle hosta.
var sekretJawny = _uslugaHaszowaniaSekretowSesji.WygenerujSekret();
uczestnik.UstawSekretSesji(_uslugaHaszowaniaSekretowSesji.Haszuj(uczestnik, sekretJawny));
_repozytoriumPoczekalni.Dodaj(uczestnik);
await _jednostkaPracy.ZapiszZmianyAsync(token);
// Token Uczestnika: kamera tak, mikrofon NIGDY na starcie -- to wymusza sam
// SerwisTokenowLiveKit przez canPublishSources:["camera"] w tokenie.
var liveKitToken = _serwisTokenowLiveKit.WygenerujToken(uczestnik.LiveKitIdentity, uczestnik.Nick, RolaUzytkownika.Uczestnik, _nazwaPokojuPoczekalnia);
_logger.LogInformation("Uczestnik {UczestnikId} dolaczyl do poczekalni wydarzenia {WydarzenieId}.", uczestnik.Id, wydarzenie.Id);
return WynikOperacji.Ok(new OdpowiedzDolaczeniaDto(uczestnik.Id, sekretJawny, _urlLiveKit, liveKitToken));
}
public async Task>> PobierzOczekujacychAsync(CancellationToken token = default)
{
var wydarzenie = await _repozytoriumWydarzen.PobierzOstatnieAsync(token);
if (wydarzenie is null)
{
// Brak wydarzenia to normalny stan (np. host jeszcze niczego nie utworzyl) -- pusta lista, nie blad.
return WynikOperacji>.Ok(new List());
}
var oczekujacy = await _repozytoriumPoczekalni.PobierzOczekujacychDlaWydarzeniaAsync(wydarzenie.Id, token);
var dto = oczekujacy
.Select(u => new UczestnikOczekujacyDto(u.Id, u.Nick, u.LiveKitIdentity, u.DolaczylUtc, u.Status))
.ToList();
return WynikOperacji>.Ok(dto);
}
public async Task OdrzucAsync(Guid uczestnikId, CancellationToken token = default)
{
var uczestnik = await _repozytoriumPoczekalni.ZnajdzPoIdAsync(uczestnikId, token);
if (uczestnik is null)
{
return WynikOperacji.Niepowodzenie("Nie znaleziono uczestnika o podanym identyfikatorze.");
}
uczestnik.Odrzuc();
await _jednostkaPracy.ZapiszZmianyAsync(token);
return WynikOperacji.Ok();
}
public async Task ZbanujAsync(Guid uczestnikId, CancellationToken token = default)
{
var uczestnik = await _repozytoriumPoczekalni.ZnajdzPoIdAsync(uczestnikId, token);
if (uczestnik is null)
{
return WynikOperacji.Niepowodzenie("Nie znaleziono uczestnika o podanym identyfikatorze.");
}
uczestnik.Zbanuj();
await _jednostkaPracy.ZapiszZmianyAsync(token);
return WynikOperacji.Ok();
}
public WynikOperacji WygenerujTokenPodgladuHosta(string nickHosta)
{
if (string.IsNullOrWhiteSpace(_urlLiveKit))
{
_logger.LogWarning("Token podgladu hosta odrzucony -- adres serwera LiveKit nie jest skonfigurowany.");
return WynikOperacji.Niepowodzenie("Usluga LiveKit nie jest jeszcze skonfigurowana na serwerze.");
}
// Host dostaje token do pokoju POCZEKALNI, nie glownej sceny -- na tym etapie tylko tu ma
// cokolwiek do podejrzenia (kamery oczekujacych, zanim ich zaakceptuje albo odrzuci).
var liveKitToken = _serwisTokenowLiveKit.WygenerujToken(nickHosta, nickHosta, RolaUzytkownika.Host, _nazwaPokojuPoczekalnia);
return WynikOperacji.Ok(new OdpowiedzTokenuLiveKitDto(liveKitToken, _urlLiveKit));
}
}