using SameBeat.Domain.Wyjatki;
namespace SameBeat.Domain.Encje;
///
/// Pojedyncza wiadomosc czatu (ETAP 6). Usunieta to soft-delete -- host "usuwa" wiadomosc z widoku
/// widzow (Usunieta=true, znika z GET /api/chat/historia i idzie ChatWiadomoscUsunieta przez SignalR),
/// ale wiersz zostaje w bazie do moderacji/audytu, dokladnie jak wzorzec HistoriaMikrofonu z ETAPU 5
/// (zapisujemy fakt biznesowy, nie usuwamy go fizycznie).
///
/// NadawcaTozsamoscId wskazuje na UczestnikCzatu (nie na UczestnikOczekujacy z poczekalni) -- to
/// "kim jest ta przegladarka na czacie". Nick jest tu zdenormalizowany (kopia z chwili wyslania), zeby
/// historia czatu nie zalezala od tego, czy tozsamosc nadawcy pozniej zostanie zbanowana.
///
public class WiadomoscCzatu
{
public const int MaksymalnaDlugoscTresci = 500;
public Guid Id { get; private set; }
public Guid WydarzenieId { get; private set; }
public Guid NadawcaTozsamoscId { get; private set; }
public string Nick { get; private set; } = string.Empty;
public string Tresc { get; private set; } = string.Empty;
public DateTime WyslanoUtc { get; private set; }
public bool Usunieta { get; private set; }
// Prywatny konstruktor bez parametrow -- wymagany przez EF Core do materializacji encji z bazy.
private WiadomoscCzatu()
{
}
private WiadomoscCzatu(Guid id, Guid wydarzenieId, Guid nadawcaTozsamoscId, string nick, string tresc, DateTime wyslanoUtc)
{
Id = id;
WydarzenieId = wydarzenieId;
NadawcaTozsamoscId = nadawcaTozsamoscId;
Nick = nick;
Tresc = tresc;
WyslanoUtc = wyslanoUtc;
}
///
/// Tworzy nowa wiadomosc. Walidacja tresci tutaj chroni przed powstaniem encji w niepoprawnym
/// stanie niezaleznie od tego, ktory serwis aplikacji jej uzyje -- SerwisCzatu odpowiada za
/// wczesniejsze sprawdzenie rate limitu/wyciszenia/bana, ta metoda pilnuje wylacznie ksztaltu tresci.
///
public static WiadomoscCzatu Wyslij(Guid wydarzenieId, Guid nadawcaTozsamoscId, string nick, string tresc)
{
if (wydarzenieId == Guid.Empty)
{
throw new NieprawidlowyStanEncjiWyjatek("Wiadomosc czatu musi byc powiazana z konkretnym wydarzeniem.");
}
if (nadawcaTozsamoscId == Guid.Empty)
{
throw new NieprawidlowyStanEncjiWyjatek("Wiadomosc czatu musi miec nadawce.");
}
if (string.IsNullOrWhiteSpace(nick))
{
throw new NieprawidlowyStanEncjiWyjatek("Wiadomosc czatu musi miec nick nadawcy.");
}
var trescOczyszczona = OczyscTresc(tresc);
return new WiadomoscCzatu(Guid.NewGuid(), wydarzenieId, nadawcaTozsamoscId, nick.Trim(), trescOczyszczona, DateTime.UtcNow);
}
private static string OczyscTresc(string tresc)
{
if (string.IsNullOrWhiteSpace(tresc))
{
throw new NieprawidlowyStanEncjiWyjatek("Tresc wiadomosci jest wymagana.");
}
var trescPrzycieta = tresc.Trim();
if (trescPrzycieta.Length > MaksymalnaDlugoscTresci)
{
throw new NieprawidlowyStanEncjiWyjatek($"Wiadomosc moze miec maksymalnie {MaksymalnaDlugoscTresci} znakow.");
}
// Podstawowa higiena wejscia -- ten sam wzorzec co przy Nick w UczestnikOczekujacy z ETAPU 2.
// To NIE jest pelne XSS-proofing (dopiero ETAP 7 dolozy CSP), tylko minimalna sanityzacja juz teraz.
if (trescPrzycieta.Contains('<') || trescPrzycieta.Contains('>'))
{
throw new NieprawidlowyStanEncjiWyjatek("Wiadomosc nie moze zawierac znakow '<' ani '>'.");
}
return trescPrzycieta;
}
/// Soft-delete -- wiersz zostaje w bazie do moderacji/audytu, tylko znika z widoku widzow. Idempotentne.
public void Usun() => Usunieta = true;
}