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

import com.atlassian.activeobjects.external.ActiveObjects;
import com.atlassian.bitbucket.ao.AbstractAoDao;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import net.java.ao.Query;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Nonnull;
import java.util.*;

import static com.atlassian.bitbucket.internal.mirroring.dao.AoConstants.*;
import static com.google.common.base.Preconditions.checkNotNull;

@Component
public class AoProjectMappingDao extends AbstractAoDao implements ProjectMappingDao {

    @Autowired
    public AoProjectMappingDao(ActiveObjects ao) {
        super(ao);
    }

    @Override
    public void clearAllProgress(@Nonnull String upstreamServerId) {
        AoProjectMapping[] mappings =
                ao.find(AoProjectMapping.class, Query.select().from(AoProjectMapping.class)
                        .where(IN_PROGRESS_COLUMN + " = ? AND " +
                               UPSTREAM_ID_COLUMN + " = ?", Boolean.TRUE, upstreamServerId));
        for (AoProjectMapping mapping : mappings) {
            mapping.setInProgress(false);
            mapping.save();
        }
    }

    @Nonnull
    @Override
    public AoProjectMapping create(@Nonnull Integer localId, @Nonnull String upstreamServerId, @Nonnull String upstreamProjectId) {
        return ao.create(AoProjectMapping.class, ImmutableMap.<String, Object>of(
                LOCAL_ID_COLUMN, localId,
                EXTERNAL_ID_COLUMN, upstreamProjectId,
                UPSTREAM_ID_COLUMN, upstreamServerId,
                IN_PROGRESS_COLUMN, true));
    }

    @Override
    public void delete(@Nonnull Integer localId) {
        ao.deleteWithSQL(AoProjectMapping.class, LOCAL_ID_COLUMN + " = ?", localId);
    }

    @Override
    public void delete(@Nonnull String upstreamServerId, @Nonnull String upstreamProjectId) {
        ao.deleteWithSQL(AoProjectMapping.class,
                UPSTREAM_ID_COLUMN + " = ? AND " + EXTERNAL_ID_COLUMN + " = ?",
                upstreamServerId, upstreamProjectId);
    }

    @Override
    public AoProjectMapping getByLocalId(@Nonnull Integer localId) {
        return ao.get(AoProjectMapping.class, localId);
    }

    @Override
    public AoProjectMapping getByUpstreamId(@Nonnull String upstreamServerId, @Nonnull String upstreamProjectId) {
        checkNotNull(upstreamServerId, "upstreamServerId");
        checkNotNull(upstreamProjectId, "upstreamProjectId");

        AoProjectMapping[] mappings = ao.find(AoProjectMapping.class, Query.select().where(
                UPSTREAM_ID_COLUMN + " = ? AND " + EXTERNAL_ID_COLUMN + " = ?",
                upstreamServerId, upstreamProjectId));

        return mappings != null && mappings.length > 0 ? mappings[0] : null;
    }

    @Override
    public Iterable<AoProjectMapping> getByUpstreamId(@Nonnull String upstreamServerId, @Nonnull final Iterable<String> upstreamProjectIds) {
        checkNotNull(upstreamServerId, "upstreamServerId");
        checkNotNull(upstreamProjectIds, "upstreamProjectIds");

        List<AoProjectMapping> mappings = new ArrayList<>();

        Iterable<List<String>> batches = Iterables.partition(upstreamProjectIds, MAX_IN_CLAUSE_SIZE);
        for (List<String> batch : batches) {
            StringBuilder whereClause = new StringBuilder();
            ArrayList<Object> arguments = new ArrayList<>();

            whereClause.append(UPSTREAM_ID_COLUMN + " = ? ");
            arguments.add(upstreamServerId);
            whereClause.append("AND " + EXTERNAL_ID_COLUMN + " IN (");
            whereClause.append(MappingDaoHelper.inClausePlaceholder(batch));
            whereClause.append(")");
            arguments.addAll(batch);
            AoProjectMapping[] aoProjectMappings = ao.find(AoProjectMapping.class,
                    Query.select().where(whereClause.toString(), arguments.toArray()).order(LOCAL_ID_COLUMN + " ASC"));
            Collections.addAll(mappings, aoProjectMappings);
        }

        return mappings;
    }

    @Nonnull
    @Override
    public Set<Integer> getLocalIdsByUpstreamId(String upstreamId) {
        Set<Integer> localProjectIds = new HashSet<>();
        ao.stream(AoProjectMapping.class, Query.select(LOCAL_ID_COLUMN).where(UPSTREAM_ID_COLUMN + " = ?", upstreamId), projectMapping -> {
            localProjectIds.add(projectMapping.getLocalId());
        });
        return localProjectIds;
    }
}
