### Техническое задание для AI-агента: Рефакторинг `ParserController`

**Задача:** Провести рефакторинг `ParserController` и связанных сервисов, чтобы сделать код более чистым, масштабируемым и простым в поддержке.

**Проблемы текущей реализации:**

1.  **Дублирование кода:** Методы `parse...RssFeed()` практически идентичны.
2.  **Нарушение SRP:** Контроллер отвечает за запуск парсеров, получение данных, статистику и проверку состояния.
3.  **Низкая масштабируемость:** Добавление нового парсера требует изменения контроллера в 3-4 местах (добавление зависимости, нового эндпоинта, обновление метода `parseAll`, обновление `scheduler/info`).

---

### План рефакторинга

#### Шаг 1: Создание общего интерфейса `ParserService` (Паттерн "Стратегия")

Создайте общий интерфейс, который будут реализовывать все парсеры. Это позволит нам работать с ними единообразно.

```java
public interface ParserService {
    /**
     * Возвращает уникальное имя источника (например, "kursiv", "kapital").
     * @return String source name
     */
    String getSourceName();

    /**
     * Запускает парсинг и сохранение данных для своего источника.
     * @return List of newly saved MarketItem
     */
    List<MarketItem> parseAndSaveRssFeed();
}
```

#### Шаг 2: Модификация существующих сервисов

Каждый из ваших сервисов (`KursivParserService`, `KapitalParserService` и т.д.) должен реализовать этот интерфейс.

**Пример для `KursivParserService`:**

```java
@Service
public class KursivParserService implements ParserService {

    @Override
    public String getSourceName() {
        return "kursiv"; // Уникальное имя в нижнем регистре
    }

    @Override
    public List<MarketItem> parseAndSaveRssFeed() {
        // ... существующая логика парсинга для Kursiv ...
    }
}
```

_Проделайте это для всех 5 парсер-сервисов._

#### Шаг 3: Создание `ParserManagerService` (Паттерн "Фасад" / Service Locator)

Создайте новый сервис, который будет управлять всеми парсерами. Spring Boot автоматически соберет все бины, реализующие `ParserService`, в один список.

```java
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import org.springframework.scheduling.annotation.Async;


@Service
public class ParserManagerService {

    private final Map<String, ParserService> parsers;

    // Spring автоматически инжектирует все бины типа ParserService
    public ParserManagerService(List<ParserService> parserServices) {
        this.parsers = parserServices.stream()
                .collect(Collectors.toMap(ParserService::getSourceName, service -> service));
    }

    /**
     * Запускает парсер по его имени.
     * @param sourceName Имя источника (например, "kursiv")
     * @return Результат парсинга
     */
    public List<MarketItem> runParser(String sourceName) {
        ParserService parser = Optional.ofNullable(parsers.get(sourceName))
                .orElseThrow(() -> new IllegalArgumentException("Парсер не найден: " + sourceName));
        return parser.parseAndSaveRssFeed();
    }

    /**
     * Асинхронно запускает все парсеры.
     * @return Список результатов для каждого парсера
     */
    @Async // Для параллельного выполнения
    public CompletableFuture<List<ParserResultDto>> runAllParsers() {
        long totalItemsBefore = marketItemService.getTotalItemsCount(); // Предполагая, что у вас есть доступ к этому сервису
        List<ParserResultDto> results = parsers.values().parallelStream()
                .map(parser -> {
                    List<MarketItem> savedItems = parser.parseAndSaveRssFeed();
                    return new ParserResultDto(parser.getSourceName(), savedItems.size(), totalItemsBefore + savedItems.size(), "completed");
                })
                .collect(Collectors.toList());
        return CompletableFuture.completedFuture(results);
    }

    public List<String> getAvailableParsers() {
        return parsers.keySet().stream().sorted().collect(Collectors.toList());
    }
}
```

_Не забудьте добавить `@EnableAsync` в главный класс вашего приложения._

#### Шаг 4: Разделение `ParserController` на несколько маленьких

Разделите один большой контроллер на три, каждый со своей зоной ответственности:

1.  **`MarketItemController`** — для публичных запросов на получение данных.
2.  **`ParserAdminController`** — для административных действий (запуск парсеров).
3.  **`HealthCheckController`** — для эндпоинтов мониторинга.

#### Шаг 5: Реализация новых контроллеров

**1. `MarketItemController.java`**
(Содержит эндпоинты, которые нужны фронтенду для отображения данных)

```java
@RestController
@RequestMapping("/api/parser/items")
public class MarketItemController {
    @Autowired private MarketItemService marketItemService;

    @GetMapping
    public ResponseEntity<ApiResponse<Page<MarketItem>>> getItems(...) { ... }

    @GetMapping("/{id}")
    public ResponseEntity<ApiResponse<MarketItem>> getItemById(@PathVariable String id) { ... }

    @GetMapping("/stats")
    public ResponseEntity<ApiResponse<ParserStatsDto>> getStats() { ... }
}
```

**2. `ParserAdminController.java`**
(Содержит эндпоинты для управления парсерами, возможно, их стоит защитить в будущем)

```java
@RestController
@RequestMapping("/api/parser/admin/parsers")
public class ParserAdminController {
    @Autowired private ParserManagerService parserManagerService;
    @Autowired private MarketItemService marketItemService; // для подсчета totalItems

    // Один динамический эндпоинт вместо пяти
    @PostMapping("/parse/{sourceName}")
    public ResponseEntity<ApiResponse<ParserResultDto>> parseSource(@PathVariable String sourceName) {
        try {
            List<MarketItem> savedItems = parserManagerService.runParser(sourceName);
            long totalItems = marketItemService.getTotalItemsCount();
            ParserResultDto result = new ParserResultDto(sourceName, savedItems.size(), totalItems, "completed");
            return ResponseEntity.ok(ApiResponse.success("Парсинг " + sourceName + " завершен", result));
        } catch (Exception e) {
            return ResponseEntity.internalServerError().body(ApiResponse.error(e.getMessage()));
        }
    }

    // Упрощенный и асинхронный метод
    @PostMapping("/parse/all")
    public ResponseEntity<ApiResponse<List<ParserResultDto>>> parseAll() {
        try {
            List<ParserResultDto> results = parserManagerService.runAllParsers().get(); // .get() для ожидания результата
            return ResponseEntity.ok(ApiResponse.success("Парсинг всех источников запущен", results));
        } catch (Exception e) {
            return ResponseEntity.internalServerError().body(ApiResponse.error(e.getMessage()));
        }
    }
}
```

**3. `HealthCheckController.java`**
(Содержит все `/health` и информационные эндпоинты)

```java
@RestController
@RequestMapping("/api/parser/health")
public class HealthCheckController {
    // ... методы healthCheck(), checkMongoConnection(), getSchedulerInfo() ...
    // Метод getSchedulerInfo можно улучшить, получая список парсеров из ParserManagerService
}
```

### Преимущества нового подхода

- **DRY (Don't Repeat Yourself):** Убрано дублирование кода в эндпоинтах.
- **SRP (Single Responsibility Principle):** Каждый контроллер отвечает за свою область.
- **Масштабируемость:** Чтобы добавить новый парсер, достаточно создать новый сервис, реализующий `ParserService`. **Контроллеры менять не нужно.**
- **Эффективность:** Запуск всех парсеров теперь может выполняться параллельно.
