11package ru .yandex .practicum .filmorate .service ;
22
33import java .time .LocalDate ;
4+ import java .util .Collections ;
5+ import java .util .Comparator ;
6+ import java .util .HashSet ;
7+ import java .util .LinkedHashSet ;
48import java .util .List ;
9+ import java .util .Objects ;
10+ import java .util .Set ;
511import lombok .RequiredArgsConstructor ;
612import lombok .extern .slf4j .Slf4j ;
713import org .springframework .stereotype .Service ;
1016// import ru.yandex.practicum.filmorate.exception.NotFoundException;
1117import ru .yandex .practicum .filmorate .exception .ValidationException ;
1218import ru .yandex .practicum .filmorate .model .Film ;
19+ import ru .yandex .practicum .filmorate .model .Genre ;
1320import ru .yandex .practicum .filmorate .storage .film .FilmStorage ;
1421import ru .yandex .practicum .filmorate .storage .user .UserStorage ;
1522
@@ -41,6 +48,7 @@ public class FilmService {
4148
4249 // CHANGE: вынесена константа самой ранней корректной даты
4350 private static final LocalDate EARLIEST_DATE = LocalDate .of (1895 , 12 , 28 );
51+ private static final int DEFAULT_POPULAR_LIMIT = FilmStorage .DEFAULT_POPULAR_LIMIT ;
4452
4553 // SPRINT 11: внедряем зависимости от интерфейсов хранилищ
4654 private final FilmStorage filmStore ;
@@ -64,6 +72,7 @@ public Film getById(final long id) {
6472
6573 public Film create (final Film film ) {
6674 validateBusinessRules (film );
75+ normalizeGenres (film );
6776 // SPRINT 11: генерация id и сохранение — в storage
6877 final Film saved = filmStore .create (film );
6978 // CHANGE: безопасный лог
@@ -76,6 +85,7 @@ public Film update(final Film film) {
7685 throw new ValidationException ("id обязателен для обновления фильма." );
7786 }
7887 validateBusinessRules (film );
88+ normalizeGenres (film );
7989 // SPRINT 11: обновление — через storage
8090 final Film saved = filmStore .update (film );
8191 // CHANGE
@@ -119,11 +129,9 @@ public void removeLike(final long filmId, final long userId) {
119129 }
120130
121131 public List <Film > getPopular (int count ) {
122- if (count <= 0 ) {
123- count = 10 ; // SPRINT 11: дефолт, если параметр не задан/некорректен
124- }
132+ final int effectiveLimit = count <= 0 ? DEFAULT_POPULAR_LIMIT : count ; // SPRINT 11: дефолт, если параметр не задан/некорректен
125133 // SPRINT 11 FIX: сортировку и лимит выполняет хранилище (для будущей БД)
126- return filmStore .findMostPopular (count );
134+ return filmStore .findMostPopular (effectiveLimit );
127135 }
128136
129137 // ----------- валидация -----------
@@ -133,5 +141,32 @@ private void validateBusinessRules(final Film film) {
133141 if (film .getReleaseDate () != null && film .getReleaseDate ().isBefore (EARLIEST_DATE )) {
134142 throw new ValidationException ("Дата релиза не может быть раньше " + EARLIEST_DATE + "." );
135143 }
144+ if (film .getMpa () == null || film .getMpa ().getId () == null ) {
145+ throw new ValidationException ("Рейтинг MPA обязателен." );
146+ }
147+ if (film .getMpa ().getId () <= 0 ) {
148+ throw new ValidationException ("Некорректный идентификатор рейтинга." );
149+ }
150+ }
151+
152+ private void normalizeGenres (final Film film ) {
153+ if (film .getGenres () == null || film .getGenres ().isEmpty ()) {
154+ film .setGenres (Collections .emptySet ());
155+ return ;
156+ }
157+ final Set <Genre > genres = film .getGenres ();
158+ final int expectedSize = Math .max (genres .size (), 1 );
159+ final Set <Integer > seen = new HashSet <>(expectedSize );
160+ final Set <Genre > normalized = new LinkedHashSet <>(expectedSize );
161+ genres .stream ()
162+ .filter (Objects ::nonNull )
163+ .filter (genre -> genre .getId () != null && genre .getId () > 0 )
164+ .sorted (Comparator .comparingInt (Genre ::getId ))
165+ .forEach (genre -> {
166+ if (seen .add (genre .getId ())) {
167+ normalized .add (new Genre (genre .getId (), genre .getName ()));
168+ }
169+ });
170+ film .setGenres (normalized );
136171 }
137172}
0 commit comments