using System.ComponentModel.DataAnnotations;
namespace Domain.Entities.Store;
///
/// 게임사 제휴 마스터. 상품/쿠폰의 필수 FK.
/// CommissionRate(게임사 몫) % 만 정의. 채널 보상률은 Channel.StoreCommissionRate 에서 채널별로 동적으로 결정.
/// antooza 몫 = 100 - Game.CommissionRate (채널 미선택) 또는 100 - Game.CommissionRate - Channel.StoreCommissionRate (채널 선택).
///
public class Game
{
[Key]
public int ID { get; private set; }
/// 게임 코드 (예: "LOL", "BG3"). UNIQUE. 외부 연동/내부 식별자.
public string? Code { get; private set; }
/// 게임 한글명 (필수, UNIQUE) — 예: "리그 오브 레전드"
public string KorName { get; private set; } = default!;
/// 게임 영문명 (선택) — 예: "League of Legends"
public string? EngName { get; private set; }
/// 게임사/퍼블리셔 (예: "Riot Games Korea")
public string Publisher { get; private set; } = default!;
/// 작은 이미지 (목록 카드/썸네일용)
public string? Thumbnail { get; private set; }
/// 큰 이미지 (상품 상세 페이지 배너, 상점 메인 배너용)
public string? BigImage { get; private set; }
/// 게임 소개 (HTML — CKEditor)
public string? Description { get; private set; }
/// 게임사 수익률(커미션) % — 0~100. 주문 금액의 이 비율을 게임사 정산 큐에 적재.
public decimal CommissionRate { get; private set; }
/// 게임 다운로드 또는 공식 사이트 주소 (선택)
public string? Link { get; private set; }
/// YouTube 예고편 URL (선택). 렌더 시 embed 로 정규화.
public string? TrailerUrl { get; private set; }
/// 최소 사양 (자유 텍스트).
public string? MinSpec { get; private set; }
/// 권장 사양 (자유 텍스트).
public string? RecommendedSpec { get; private set; }
/// 게임 출시일 (선택).
public DateOnly? ReleaseDate { get; private set; }
public virtual List GameImage { get; private set; } = [];
public virtual List GameGenreMap { get; private set; } = [];
public virtual List GameCategoryMap { get; private set; } = [];
public virtual List GameLanguageSupport { get; private set; } = [];
public bool IsActive { get; private set; } = true;
public int Order { get; private set; }
public DateTime CreatedAt { get; private set; } = DateTime.UtcNow;
public DateTime? UpdatedAt { get; private set; }
private Game() { }
public static Game Create(
string korName,
string publisher,
decimal commissionRate,
string? code = null,
string? engName = null,
string? thumbnail = null,
string? bigImage = null,
string? description = null,
string? link = null,
int order = 0
) {
ValidateRate(commissionRate);
ValidateNames(korName, engName);
ValidatePublisher(publisher);
ValidateCode(code);
ValidateLink(link);
return new Game
{
Code = NormalizeOptional(code),
KorName = korName.Trim(),
EngName = NormalizeOptional(engName),
Publisher = publisher.Trim(),
CommissionRate = commissionRate,
Thumbnail = thumbnail,
BigImage = bigImage,
Description = description,
Link = NormalizeOptional(link),
Order = order
};
}
public void Update(
string korName,
string publisher,
decimal commissionRate,
string? code,
string? engName,
string? thumbnail,
string? bigImage,
string? description,
string? link,
int order,
bool isActive
) {
ValidateRate(commissionRate);
ValidateNames(korName, engName);
ValidatePublisher(publisher);
ValidateCode(code);
ValidateLink(link);
Code = NormalizeOptional(code);
KorName = korName.Trim();
EngName = NormalizeOptional(engName);
Publisher = publisher.Trim();
CommissionRate = commissionRate;
Thumbnail = thumbnail;
BigImage = bigImage;
Description = description;
Link = NormalizeOptional(link);
Order = order;
IsActive = isActive;
UpdatedAt = DateTime.UtcNow;
}
public void Deactivate()
{
IsActive = false;
UpdatedAt = DateTime.UtcNow;
}
/// 상세 미디어/사양 설정 (Admin 게임 상세 폼 전용). Create/Update 시그니처 변경 회피.
public void SetDetail(string? trailerUrl, string? minSpec, string? recommendedSpec, DateOnly? releaseDate)
{
if (trailerUrl is not null && trailerUrl.Length > 500)
{
throw new ArgumentOutOfRangeException(nameof(trailerUrl), "TrailerUrl 은 500자 이하로 입력하세요.");
}
TrailerUrl = NormalizeOptional(trailerUrl);
MinSpec = NormalizeOptional(minSpec);
RecommendedSpec = NormalizeOptional(recommendedSpec);
ReleaseDate = releaseDate;
UpdatedAt = DateTime.UtcNow;
}
private static void ValidateRate(decimal commissionRate)
{
if (commissionRate < 0 || commissionRate > 100)
{
throw new ArgumentOutOfRangeException(nameof(commissionRate), "0~100 사이의 값이어야 합니다.");
}
}
private static void ValidateNames(string korName, string? engName)
{
if (string.IsNullOrWhiteSpace(korName))
{
throw new ArgumentException("KorName is required.", nameof(korName));
}
if (korName.Length > 255)
{
throw new ArgumentOutOfRangeException(nameof(korName), "KorName 은 255자 이하로 입력하세요.");
}
if (engName is not null && engName.Length > 255)
{
throw new ArgumentOutOfRangeException(nameof(engName), "EngName 은 255자 이하로 입력하세요.");
}
}
private static void ValidatePublisher(string publisher)
{
if (string.IsNullOrWhiteSpace(publisher))
{
throw new ArgumentException("Publisher is required.", nameof(publisher));
}
if (publisher.Length > 100)
{
throw new ArgumentOutOfRangeException(nameof(publisher), "Publisher 는 100자 이하로 입력하세요.");
}
}
private static void ValidateCode(string? code)
{
if (code is not null && code.Length > 6)
{
throw new ArgumentOutOfRangeException(nameof(code), "Code 는 6자 이하로 입력하세요.");
}
}
private static void ValidateLink(string? link)
{
if (link is not null && link.Length > 255)
{
throw new ArgumentOutOfRangeException(nameof(link), "Link 는 255자 이하로 입력하세요.");
}
}
private static string? NormalizeOptional(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
return value.Trim();
}
}