package org.takiguchi.cerberus.cerberusapp.service;

import org.springframework.stereotype.Service;
import org.takiguchi.cerberus.cerberusapp.exception.NotFoundException;
import org.takiguchi.cerberus.cerberusapp.model.Application;
import org.takiguchi.cerberus.cerberusapp.model.ApplicationStatus;
import org.takiguchi.cerberus.cerberusapp.model.ServiceStatus;
import org.takiguchi.cerberus.cerberusapp.service.servicemanager.ServiceManagerProvider;
import org.takiguchi.cerberus.cerberusapp.service.validator.ApplicationValidator;

import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;

import static org.takiguchi.cerberus.cerberusapp.model.Application.anApplication;
import static org.takiguchi.cerberus.cerberusapp.model.ApplicationStatus.anApplicationStatus;

@Service
public class ApplicationService {
    private final ApplicationValidator applicationValidator;
    private final ApplicationRepositoryPort applicationRepositoryPort;
    private final ServiceManagerProvider serviceManagerProvider;

    public ApplicationService(ApplicationValidator applicationValidator,
                              ApplicationRepositoryPort applicationRepositoryPort,
                              ServiceManagerProvider serviceManagerProvider) {
        this.applicationValidator = applicationValidator;
        this.applicationRepositoryPort = applicationRepositoryPort;
        this.serviceManagerProvider = serviceManagerProvider;
    }

    public Optional<Application> getById(UUID applicationId) {
        return applicationRepositoryPort.getById(applicationId);
    }

    public List<Application> getAll() {
        return applicationRepositoryPort.getAll();
    }

    public Application add(Application application) {
        applicationValidator.validate(application);
        return applicationRepositoryPort.add(application);
    }

    public void update(Application application) {
        applicationRepositoryPort.getById(application.getId())
                .map(existingApplication -> anApplication()
                        .withId(existingApplication.getId())
                        .withName(application.getName())
                        .withServiceName(application.getServiceName())
                        .withServiceType(application.getServiceType())
                        .build()
                )
                .ifPresentOrElse(this::validateThenSave, NotFoundException::new);
    }

    private void validateThenSave(Application updatedApplication) {
        applicationValidator.validate(updatedApplication);
        applicationRepositoryPort.update(updatedApplication);
    }

    public void remove(UUID applicationId) {
        applicationRepositoryPort.remove(applicationId);
    }

    public ServiceStatus checkStatus(UUID applicationId) {
        return applicationRepositoryPort.getById(applicationId)
                .map(application -> serviceManagerProvider.getServiceManager(application)
                        .getStatus(application)
                ).orElseThrow(NotFoundException::new);
    }

    public void start(UUID applicationId) {
        applicationRepositoryPort.getById(applicationId)
                .ifPresentOrElse(
                        application -> serviceManagerProvider.getServiceManager(application)
                                .start(application),
                        NotFoundException::new
                );
    }

    public void stop(UUID applicationId) {
        applicationRepositoryPort.getById(applicationId)
                .ifPresentOrElse(
                        application -> serviceManagerProvider.getServiceManager(application)
                                .stop(application),
                        NotFoundException::new
                );
    }

    public void restart(UUID applicationId) {
        applicationRepositoryPort.getById(applicationId)
                .ifPresentOrElse(
                        application -> serviceManagerProvider.getServiceManager(application)
                                .restart(application),
                        NotFoundException::new
                );
    }

    public List<ApplicationStatus> checkAllStatus() {
        return getAll().stream()
                .map(application -> {
                    ServiceStatus status = checkStatus(application.getId());
                    return anApplicationStatus()
                            .withApplication(application)
                            .withStatus(status)
                            .build();
                })
                .collect(Collectors.toList());
    }
}
