package com.atlassian.bitbucket.internal.mirroring.mirror;

import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.internal.mirroring.mirror.dao.AoMirroredProject;
import com.atlassian.bitbucket.internal.mirroring.mirror.dao.AoUpstreamServer;
import com.atlassian.bitbucket.internal.mirroring.mirror.dao.AoUpstreamSettings;
import com.atlassian.bitbucket.internal.mirroring.mirror.dao.UpstreamServerDao;
import com.atlassian.bitbucket.mirroring.mirror.*;
import com.atlassian.event.api.EventPublisher;
import com.atlassian.sal.api.transaction.TransactionTemplate;

import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.Optional.ofNullable;

public class DefaultUpstreamSettingsService implements UpstreamSettingsService {

    private final EventPublisher eventPublisher;
    private final I18nService i18nService;
    private final TransactionTemplate transactionTemplate;
    private final UpstreamServerDao upstreamServerDao;
    private final MirroringValidationHelper validationHelper;

    public DefaultUpstreamSettingsService(EventPublisher eventPublisher, I18nService i18nService,
                                          TransactionTemplate transactionTemplate, UpstreamServerDao upstreamServerDao,
                                          MirroringValidationHelper validationHelper) {
        this.eventPublisher = eventPublisher;
        this.i18nService = i18nService;
        this.transactionTemplate = transactionTemplate;
        this.upstreamServerDao = upstreamServerDao;
        this.validationHelper = validationHelper;
    }

    @Nonnull
    @Override
    public Optional<UpstreamSettings> getSettings(@Nonnull String upstreamId) {
        checkNotNull(upstreamId, "upstreamId");

        validationHelper.checkIsUpstreamUserOrAdmin(upstreamId);
        return ofNullable(transactionTemplate.execute(() ->
                toMirrorSettings(upstreamServerDao.getSettingsById(upstreamId))));
    }

    @Nonnull
    @Override
    public UpstreamSettings setSettings(@Nonnull UpstreamSettingsSetRequest request) {
        checkNotNull(request, "request");

        return updateSettings(request.getUpstreamId(), () ->
                upstreamServerDao.updateSettings(request.getUpstreamId(), request.getMode(), request.getProjectIds()));
    }

    @Nonnull
    @Override
    public UpstreamSettings updateSettings(@Nonnull UpstreamSettingsUpdateRequest request) {
        checkNotNull(request, "request");

        return updateSettings(request.getUpstreamId(), () ->
                upstreamServerDao.updateSettings(
                        request.getUpstreamId(), request.getMode(),
                        request.getAddedProjects(), request.getRemovedProjects()));
    }

    private NoSuchUpstreamException throwNoSuchUpstream(String upstreamId) {
        throw new NoSuchUpstreamException(i18nService.createKeyedMessage("bitbucket.mirroring.no.such.upstream.server", upstreamId));
    }

    private UpstreamSettings toMirrorSettings(AoUpstreamSettings settings) {
        if (settings == null) {
            return null;
        }

        MirroringMode mode = MirroringMode.fromId(settings.getMode());
        SimpleUpstreamSettings.Builder builder = new SimpleUpstreamSettings.Builder()
                .mode(mode);

        if (mode == MirroringMode.ALL_PROJECTS) {
            return builder.build();
        }

        AoMirroredProject[] mirroredProjects = settings.getMirroredProjects();
        if (mirroredProjects != null && mirroredProjects.length > 0) {
            List<String> projectIds = Arrays.stream(settings.getMirroredProjects())
                    .map(AoMirroredProject::getExternalId)
                    .collect(Collectors.toList());
            builder.projectIds(projectIds);
        }
        return builder.build();
    }

    private UpstreamSettings updateSettings(String upstreamId, Supplier<AoUpstreamSettings> update) {
        validationHelper.checkIsUpstreamUserOrAdmin(upstreamId);

        return transactionTemplate.execute(() -> {
            AoUpstreamServer upstreamServer = upstreamServerDao.getById(upstreamId);
            if (upstreamServer == null) {
                throw throwNoSuchUpstream(upstreamId);
            }

            UpstreamSettings oldSettings = toMirrorSettings(upstreamServerDao.getSettingsById(upstreamId));
            UpstreamSettings newSettings = toMirrorSettings(update.get());
            if (newSettings == null) {
                // this shouldn't happen - should be detected above when getById is called
                throw throwNoSuchUpstream(upstreamId);
            }

            if (!newSettings.equals(oldSettings)) {
                upstreamServer.setLastSettingsChangeDate(new Date());
                upstreamServer.save();
                eventPublisher.publish(new UpstreamSettingsChangedEvent(
                        DefaultUpstreamSettingsService.this, upstreamServer,
                        oldSettings, newSettings));
            }

            return newSettings;
        });
    }
}
